J'essaie de comprendre comment optimiser une requête très lente dans MySQL (je n'ai pas conçu cela):
SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391';
+----------+
| COUNT(*) |
+----------+
| 3224022 |
+----------+
1 row in set (1 min 0.16 sec)
En comparant cela à un décompte complet:
select count(*) from change_event;
+----------+
| count(*) |
+----------+
| 6069102 |
+----------+
1 row in set (4.21 sec)
La déclaration d'explication ne m'aide pas ici:
explain SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: me
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 4120213
Extra: Using where; Using index
1 row in set (0.00 sec)
OK, il pense toujours qu'il a besoin d'environ 4 millions d'entrées pour compter, mais je pourrais compter les lignes dans un fichier plus rapidement que ça! Je ne comprends pas pourquoi MySQL prend autant de temps.
Voici la définition du tableau:
CREATE TABLE `change_event` (
`change_event_id` bigint(20) NOT NULL default '0',
`timestamp` datetime NOT NULL,
`change_type` enum('create','update','delete','noop') default NULL,
`changed_object_type` enum('Brand','Broadcast','Episode','OnDemand') NOT NULL,
`changed_object_id` varchar(255) default NULL,
`changed_object_modified` datetime NOT NULL default '1000-01-01 00:00:00',
`modified` datetime NOT NULL default '1000-01-01 00:00:00',
`created` datetime NOT NULL default '1000-01-01 00:00:00',
`pid` char(15) default NULL,
`episode_pid` char(15) default NULL,
`import_id` int(11) NOT NULL,
`status` enum('success','failure') NOT NULL,
`xml_diff` text,
`node_digest` char(32) default NULL,
PRIMARY KEY (`change_event_id`),
KEY `idx_change_events_changed_object_id` (`changed_object_id`),
KEY `idx_change_events_episode_pid` (`episode_pid`),
KEY `fk_import_id` (`import_id`),
KEY `idx_change_event_timestamp_ce_id` (`timestamp`,`change_event_id`),
KEY `idx_change_event_status` (`status`),
CONSTRAINT `fk_change_event_import` FOREIGN KEY (`import_id`) REFERENCES `import` (`import_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Version:
$ mysql --version
mysql Ver 14.12 Distrib 5.0.37, for pc-solaris2.8 (i386) using readline 5.0
Y a-t-il quelque chose d'évident qui me manque? (Oui, j'ai déjà essayé "SELECT COUNT (change_event_id)", mais il n'y a pas de différence de performance).
InnoDB utilise des clés primaires en cluster, de sorte que la clé primaire est stockée avec la ligne dans les pages de données, et non dans des pages d'index distinctes. Pour effectuer une analyse de plage, vous devez toujours parcourir toutes les lignes potentiellement larges des pages de données; notez que ce tableau contient une colonne TEXT.
J'essaierais deux choses:
optimize table
. Cela garantira que les pages de données sont stockées physiquement dans un ordre trié. Cela pourrait en théorie accélérer une analyse de plage sur une clé primaire en cluster.(vous voulez aussi probablement faire en sorte que la colonne change_event_id soit bigint non signé si elle incrémente à partir de zéro)
Voici quelques choses que je suggère:
Changez la colonne de "bigint" en "int unsigned". Vous attendez-vous vraiment à avoir plus de 4,2 milliards d'enregistrements dans ce tableau? Sinon, vous perdez de l'espace (et du temps) sur le champ extra-large. Les index MySQL sont plus efficaces sur les types de données plus petits.
Exécutez la commande " OPTIMIZE TABLE " et voyez si votre requête est plus rapide par la suite.
Vous pouvez également envisager partitionner votre table selon le champ ID, en particulier si les enregistrements plus anciens (avec des valeurs d'ID inférieures) deviennent moins pertinents au fil du temps. Une table partitionnée peut souvent exécuter des requêtes agrégées plus rapidement qu'une énorme table non partitionnée.
ÉDITER:
En regardant de plus près cette table, elle ressemble à une table de style journalisation, où les lignes sont insérées mais jamais modifiées.
Si c'est vrai, vous n'aurez peut-être pas besoin de toute la sécurité transactionnelle fournie par le moteur de stockage InnoDB, et vous pourrez peut-être vous en sortir avec passer à MyISAM , ce qui est considérablement plus efficace pour les requêtes agrégées.
J'ai rencontré un comportement comme celui-ci auparavant avec des bases de données de géolocalisation IP. Au-delà d'un certain nombre d'enregistrements, la capacité de MySQL à tirer le meilleur parti des index pour les requêtes basées sur des plages s'évapore apparemment. Avec les bases de données de géolocalisation, nous les avons traitées en segmentant les données en segments suffisamment raisonnables pour permettre l'utilisation des index.
Vérifiez la fragmentation de vos index. Dans mon entreprise, nous avons un processus d'importation nocturne qui supprime nos index et, au fil du temps, il peut avoir un impact profond sur les vitesses d'accès aux données. Par exemple, nous avions une procédure SQL qui prenait 2 heures pour s'exécuter un jour après la décomposition des index, cela prenait 3 minutes. nous utilisons SQL Server 2005 mal chercher un script qui peut vérifier cela sur MySQL.
Mise à jour: consultez ce lien: http://dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html
Courir "analyze table_name
"sur cette table - il est possible que les indices ne soient plus optimaux.
Vous pouvez souvent le dire en exécutant "show index from table_name
". Si la valeur de cardinalité est NULL
, vous devez alors forcer une nouvelle analyse.
MySQL dit d'abord "Utiliser où", car il a besoin de lire tous les enregistrements/valeurs des données d'index pour les compter réellement. Avec InnoDb, il essaie également de "saisir" cette plage record de 4 mil pour le compter.
Vous devrez peut-être tester différents niveaux d'isolement des transactions: http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-uncommitted
et voir lequel est le meilleur.
Avec MyISAM, ce serait juste rapide, mais avec un modèle d'écriture intensif, cela entraînera des problèmes de verrouillage.
Je créerais une table de "compteurs" et ajouterais des déclencheurs "créer une ligne"/"supprimer une ligne" à la table que vous comptez. Les déclencheurs devraient augmenter/diminuer les valeurs de comptage sur la table des "compteurs" à chaque insertion/suppression, vous n'aurez donc pas besoin de les calculer chaque fois que vous en aurez besoin.
Vous pouvez également accomplir cela du côté de l'application en mettant en cache les compteurs, mais cela impliquera de vider le "cache du compteur" à chaque insertion/suppression.
Pour une référence, jetez un œil à ceci http://pure.rednoize.com/2007/04/03/mysql-performance-use-counter-tables/