Comment puis-je forcer MySQL à IGNORER TOUS les index?


12

J'ai lu des articles sur l' FORCEindex, mais comment puis-je forcer MySQL à IGNORE ALLindexer?

J'ai essayé SELECT * FROM tbl IGNORE INDEX(*), mais je n'ai pas réussi.

Quant à savoir pourquoi j'ai (et d'autres) besoin de le faire: Par exemple, j'avais besoin de résumer les statistiques des référents par tld comme ceci:

SELECT 
    count(*) as c, 
    SUBSTRING
    (
        domain_name, 
        LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2
    ) as tld
FROM `domains_import` 
    IGNORE INDEX(domain_name)
GROUP BY tld
ORDER BY c desc
LIMIT 100

... mais je dois toujours regarder quels index sont définis ou déterminer quel index sera utilisé via Explain. Il serait très pratique d'écrire IGNORE INDEX ALLsimplement et de ne pas s'en soucier.

Est-ce que quelqu'un connaît la syntaxe ou un hack? (Des dizaines de lignes via les tables de définition MySQL ne sont vraiment pas un raccourci).

Ajouté à partir de la discussion par chat :

Bechmark:

  • pas d'index = 148,5 secondes

  • avec index = 180 secondes et toujours en cours d'exécution avec l'envoi de données La baie SSD est si puissante que vous ne vous souciez presque pas du cache de données ...

Définition de référence:

CREATE TABLE IF NOT EXISTS `domains_import` (
`domain_id` bigint(20) unsigned NOT NULL,
`domain_name` varchar(253) CHARACTER SET ascii COLLATE ascii_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `domains_import`
ADD PRIMARY KEY (`domain_id`),
ADD UNIQUE KEY `domain_name` (`domain_name`);

ALTER TABLE `domains_import`
MODIFY `domain_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT;

InnoDB, le test avec index (pas USE INDEX () ou similaire) est toujours en cours d'exécution, 250 secondes, je viens de le tuer.

Réponses:


24

Il n'est absolument pas clair pourquoi vous le souhaitez, mais vous pouvez utiliser l'indice USE INDEX ()pour dire à l'optimiseur de ne pas utiliser d'index. Depuis les documents MySQL: index hints

Il est syntaxiquement valide d' omettre index_listpourUSE INDEX , ce qui signifie «n'utilisez aucun index». Omettre index_list pour FORCE INDEXou IGNORE INDEXest une erreur de syntaxe.

Votre requête devient:

SELECT count(*) AS c, 
       substring_index(domain_name, '.', -1) AS tld
FROM domains_import 
       USE INDEX ()        -- use no indexes
GROUP BY tld
ORDER BY c DESC
LIMIT 100 ;

Note complémentaire: l'expression complexe:

SUBSTRING(domain_name, LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2) 

peut être simplifié de 4 appels de fonction à 1:

SUBSTRING_INDEX(domain_name, '.', -1)

1
Cela m'a été utile lorsque l'optimiseur MySQL 5.7.10 a changé son plan d'interrogation pour le pire lors de la suppression d'une partie de ce LEFT JOINque j'avais. `USE INDEX ()` a obligé MySQL à faire un scan de table sur une table de 20K lignes et 1 à 1 JOINs au lieu de croiser 500 lignes entre deux index. 20 fois plus rapide.
Xenos

2

Vous pouvez également intégrer WHERE 1=1

SELECT 
    count(*) as c, 
    SUBSTRING
    (
        domain_name, 
        LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2
    ) as tld
FROM `domains_import` 
WHERE 1=1
GROUP BY tld
ORDER BY c desc
LIMIT 100

ypercube vient de me demander

Rolando, l'optimiseur de MySQL est-il si stupide qu'une simple condition toujours vraie interdira l'utilisation des index?

Oui, mais vous avez donné à MySQL une requête vraiment stupide. 1=1reviendrait à l'indice clusterisé. Nonobstant, il existe encore un autre moyen, mais cela nécessite d'être un peu malveillant pour l'Optimizer.

SELECT 
    count(*) as c, 
    SUBSTRING
    (
        domain_name, 
        LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2
    ) as tld
FROM `domains_import` 
WHERE domain_name = domain_name
GROUP BY tld
ORDER BY c desc
LIMIT 100

Cela jettera chaque index sous le bus à coup sûr, car la valeur de chaque ligne doit domain_nameêtre vérifiée. Si domain_nameest indexé, vous devez choisir une colonne pour celle WHERE column_name=column_namequi n'est pas indexée du tout.

Je viens d'essayer cela sur une grande table dans un serveur de transfert

mysql > explain SELECT COUNT(1) FROM VIDEO WHERE EMBEDDED_FLG=EMBEDDED_FLG;
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | VIDEO | ALL  | NULL          | NULL | NULL    | NULL | 354327 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)

Aucun index n'est choisi


Rolando, l'optimiseur de MySQL est-il si stupide qu'une simple condition toujours vraie interdira l'utilisation des index?
ypercubeᵀᴹ

@ypercube oui, mais vous devez réduire la requête suffisamment pour que cela se produise.
RolandoMySQLDBA

1
Hé, j'ai voté moi-même la réponse de yercube. Ma réponse est juste une autre façon et explique l'échappatoire de l'Optimizer.
RolandoMySQLDBA

1
Rolando, pas vrai: L'index sera utilisé: SQLfiddle . Même si vous faites quelque chose de plus compliqué, comme WHERE id+0 = id*1l'index sera toujours utilisé, et un extra Using whereapparaîtra.
ypercubeᵀᴹ

4
@PaulWhite, ce serait le cas. (c'est idiot mais pas idiot;) Et c'est peut-être pourquoi la requête de Roalndo n'utilise pas d'index, la colonne doit avoir été définie comme NULL.
ypercubeᵀᴹ

0

En supposant que vous ayez ces deux index:

ADD PRIMARY KEY (`domain_id`),
ADD UNIQUE KEY `domain_name` (`domain_name`);

Peu importe alors ce que fait l'optimiseur; il doit balayer essentiellement une quantité identique de choses.

Cas 1: il effectue une analyse de table (ou utilise domain_id): il analysera les paires (id, nom), localisera tous les noms, effectuera SUBSTRING..LOCATE, GROUP BY et enfin ORDER BY. Le GROUP BY et le ORDER BY ont probablement besoin chacun d'une table tmp et d'un tri de fichiers. Vérifiez EXPLAIN SELECT ...si c'est le cas.

Cas 2: il effectue un balayage d'index (de nom_domaine): cet index contient en fait des paires (nom, id) - car InnoDB place implicitement le PK à la fin de toute clé secondaire. Le reste du traitement est parallèle au cas 1.

Une chose pourrait être différente - la taille des deux BTrees. Faites SHOW TABLE STATUS LIKE domains_importpour voir la Data_length (pour le cas 1) et Index_length (pour le cas 2). Le plus grand BTree sera plus lent.

Une autre chose pourrait être différente - la mise en cache. Quelle est la valeur de innodb_buffer_pool_size? De combien de RAM disposez-vous? Les données (ou index) peuvent-elles être contenues dans le pool de tampons? (Ou en sera-t-il à 37%, car il s'agit d'une analyse de table / index?) Si cela convient, exécutez la requête deux fois. La deuxième fois sera environ 10 fois plus rapide car il ne frappera pas le disque (mise en cache).

S'il s'agit d'une tâche unique, le SSD vous aidera. Si ce n'est pas le cas, et que vous pouvez mettre en cache la table entière, cela n'aidera pas après le chargement du buffer_pool.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.