Voici ma table avec ~ 10 000 000 de données de lignes
CREATE TABLE `votes` (
`subject_name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`subject_id` int(11) NOT NULL,
`voter_id` int(11) NOT NULL,
`rate` int(11) NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`subject_name`,`subject_id`,`voter_id`),
KEY `IDX_518B7ACFEBB4B8AD` (`voter_id`),
KEY `subject_timestamp` (`subject_name`,`subject_id`,`updated_at`),
KEY `voter_timestamp` (`voter_id`,`updated_at`),
CONSTRAINT `FK_518B7ACFEBB4B8AD` FOREIGN KEY (`voter_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Voici les index cardinalités
Donc, quand je fais cette requête:
SELECT SQL_NO_CACHE * FROM votes WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Je m'attendais à ce qu'il utilise index voter_timestamp
mais mysql choisit de l'utiliser à la place:
explain select SQL_NO_CACHE * from votes where subject_name = 'medium' and voter_id = 1001 and rate = 1 order by updated_at desc limit 20 offset 100;`
type:
index_merge
possible_keys:
PRIMARY,IDX_518B7ACFEBB4B8AD,subject_timestamp,voter_timestamp
key:
IDX_518B7ACFEBB4B8AD,PRIMARY
key_len:
102,98
ref:
NULL
rows:
9255
filtered:
10.00
Extra:
Using intersect(IDX_518B7ACFEBB4B8AD,PRIMARY); Using where; Using filesort
Et j'ai eu un temps de requête de 200 à 400 ms.
Si je le force à utiliser le bon index comme:
SELECT SQL_NO_CACHE * FROM votes USE INDEX (voter_timestamp) WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Mysql peut retourner les résultats en 1-2 ms
et voici l'expliquer:
type:
ref
possible_keys:
voter_timestamp
key:
voter_timestamp
key_len:
4
ref:
const
rows:
18714
filtered:
1.00
Extra:
Using where
Alors pourquoi mysql n'a-t-il pas choisi l' voter_timestamp
index pour ma requête d'origine?
Ce que j'avais essayé analyze table votes
, optimize table votes
c'est de supprimer cet index et de l'ajouter à nouveau, mais mysql utilise toujours le mauvais index. ne sais pas vraiment quel est le problème.
subject_name = "medium"
pièce, elle peut également choisir le bon index, pas besoin d'indexerrate
(voter_id, updated_at)
. Un autre indice serait(voter_id, subject_name, updated_at)
ou(subject_name, voter_id, updated_at)
(sans le taux).subject_name='medium' and rate=1
)LIMIT
ni même leORDER BY
sauf si l'index satisfait d'abord tout le filtrage. Autrement dit, sans les 4 colonnes complètes, il collectera toutes les lignes pertinentes, les triera toutes, puis sélectionnera leLIMIT
. Avec l'index à 4 colonnes, la requête peut éviter le tri et s'arrêter après avoir lu uniquement lesLIMIT
lignes.Réponses:
MySQL utilise un modèle de coût relativement simple (plus simple que les autres SGBDR) pour planifier les requêtes dans lesquelles le filtrage de votre ensemble de données a une priorité assez élevée. Dans votre première requête avec l'index de fusion, il est estimé que la numérisation ~ 9000 lignes va être nécessaire tandis que la seconde avec l'index d'index exigera 18000. Je parie que cela pèse suffisamment dans le calcul pour déplacer l'échelle vers la fusion. . Vous pouvez le confirmer (ou trouver d'autres raisons) en activant
optimizer_trace
, exécutez votre requête et évaluez les résultats.Une remarque sur
index_merge
: dans la plupart des cas, vous constaterez que c'est assez cher. Bien que très utile pour les scénarios de type OLAP, il peut ne pas être très bien adapté à OLTP car l'opération peut prendre un temps considérable de votre requête et comme vous pouvez le voir parfois, le plan d'exécution sous-optimal est en fait plus rapide.Heureusement, MySQL fournit des commutateurs pour l'optimiseur afin que vous puissiez le personnaliser à votre guise.
Pour toutes les options que vous pouvez exécuter:
Pour en changer un, vous n'avez pas besoin de copier-coller toute la chaîne. Cela fonctionne comme
dict.update()
en python.Si possible, je voudrais également jeter un œil à la structure de votre table et m'améliorer. Il n'est pas vraiment conseillé d'avoir une clé primaire de ~ 100 octets avec de nombreuses clés secondaires.
Vous avez quatre clés secondaires et certaines d'entre elles sont superflues, par exemple, l'
(voter_id)
index est un sous-ensemble de(voter_id, updated_at)
la source
OR
enUNION
est souvent aussi bon ou meilleur.Pour cette requête, vous avez besoin de cet index:
Le
updated_at
doit être le dernier; les trois autres peuvent être dans n'importe quel ordre. (Les index à 3 colonnes de ypercube ne sont pas très utiles car ils ne terminent pas lesWHERE
colonnes avant de frapper laORDER BY
colonne.)Lorsque vous ajoutez cet index, vous pouvez probablement vous débarrasser de toutes les autres clés secondaires:
KEY
IDX_518B7ACFEBB4B8AD
(voter_id
), - Le FK peut utiliser mon index de clésubject_timestamp
(subject_name
,subject_id
,updated_at
), - KEY essentiellement redondantevoter_timestamp
(voter_id
,updated_at
), - peut avoir été votre tentativeAvec l'index à 4 colonnes, vous avez une chance d'optimiser la "pagination" et d'éviter
OFFSET
. Voir ce blog.Sur un autre sujet ... Quand je vois
X_name
etX_id
, je suppose que la "normalisation" est en cours. Je m'attendrais à voir ces deux colonnes dans un tableau, avec pratiquement rien d'autre. Je ne m'attendrais pas à voir les deux dans un autre tableau.(voter_id, updated_at)
ne passera pasvoter_id
car il n'a pas fini de filtrer (leWHERE
). Puis, comme l'autre index est plus petit, il est choisi. Le mien a 3 colonnes pour s'occuper du filtrage, puis la colonne pourORDER BY
.la source