Comment concevoir des index pour des colonnes avec des valeurs NULL dans MySQL?

11

J'ai une base de données avec 40 millions d'entrées et je souhaite exécuter des requêtes avec la WHEREclause suivante

...
WHERE
  `POP1` IS NOT NULL 
  && `VT`='ABC'
  && (`SOURCE`='HOME')
  && (`alt` RLIKE '^[AaCcGgTt]$')
  && (`ref` RLIKE '^[AaCcGgTt]$')
  && (`AA` RLIKE '^[AaCcGgTt]$')
  && (`ref` = `AA` || `alt` = `AA`)
LIMIT 10 ;

POP1est une colonne flottante qui peut également être NULL. POP1 IS NOT NULLdevrait exclure environ 50% des entrées, c'est pourquoi je l'ai mis au début. Tous les autres termes ne réduisent le nombre que marginalement.

Entre autres, j'ai conçu un index pop1_vt_source, qui semble ne pas être utilisé, tandis qu'un index avec vtcomme première colonne est utilisé. Sortie EXPLAIN:

| id | select_type | table | type | possible_keys                          | key                 | key_len | ref         | rows     | Extra       |
|  1 | SIMPLE      | myTab | ref  | vt_source_pop1_pop2,pop1_vt_source,... | vt_source_pop1_pop2 | 206     | const,const | 20040021 | Using where |

Pourquoi l'index avec pop1comme première colonne n'est-il pas utilisé? À cause de NOTou à cause de NULLen général. Comment puis-je améliorer la conception de mes indices et des clauses WHERE? Même en limitant à 10 entrées, la requête prend plus de 30 secondes, bien que les 100 premières entrées du tableau doivent contenir les 10 correspondances.

Sven
la source

Réponses:

10

C'est le NOT NULL:

CREATE TEMPORARY TABLE `myTab` (`notnul` FLOAT, `nul` FLOAT);
INSERT INTO `myTab` VALUES (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2);
SELECT * FROM `myTab`;

donne:

+--------+------+
| notnul | nul  |
+--------+------+
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
+--------+------+

Créez l'index:

CREATE INDEX `notnul_nul` ON `myTab` (`notnul`, `nul`);
CREATE INDEX `nul_notnul` ON `myTab` (`nul`, `notnul`);

SHOW INDEX FROM `myTab`;

donne:

+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| myTab |          1 | notnul_nul |            1 | notnul      | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
| myTab |          1 | notnul_nul |            2 | nul         | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
| myTab |          1 | nul_notnul |            1 | nul         | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
| myTab |          1 | nul_notnul |            2 | notnul      | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

expliquer maintenant les sélections. Il semble que MySQL utilise l'index, même si vous utilisez NOT NULL:

EXPLAIN SELECT * FROM `myTab` WHERE `notnul` IS NOT NULL;
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+ 
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+ 
|  1 | SIMPLE      | myTab | index | notnul_nul    | notnul_nul | 10      | NULL |   12 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+


EXPLAIN SELECT * FROM `myTab` WHERE `nul` IS NOT NULL;
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | myTab | range | nul_notnul    | nul_notnul | 5       | NULL |    6 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Mais, en comparant NOT NULLet NULL, il semble que MySQL préfère d'autres index lors de l'utilisation NOT NULL. Bien que cela n'ajoute évidemment aucune information. C'est parce que MySQL interprète NOT NULLcomme une plage comme vous pouvez le voir dans la colonne type. Je ne suis pas sûr S'il y a une solution:

EXPLAIN SELECT * FROM `myTab` WHERE `nul` IS NULL && notnul=2;
+----+-------------+-------+------+-----------------------+------------+---------+-------------+------+--------------------------+
| id | select_type | table | type | possible_keys         | key        | key_len | ref         | rows | Extra                    |
+----+-------------+-------+------+-----------------------+------------+---------+-------------+------+--------------------------+
|  1 | SIMPLE      | myTab | ref  | notnul_nul,nul_notnul | notnul_nul | 10      | const,const |    1 | Using where; Using index |
+----+-------------+-------+------+-----------------------+------------+---------+-------------+------+--------------------------+


EXPLAIN SELECT * FROM `myTab` WHERE `nul` IS NOT NULL && notnul=2;
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys         | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | myTab | range | notnul_nul,nul_notnul | notnul_nul | 10      | NULL |    1 | Using where; Using index |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+

Je pense qu'il pourrait y avoir une meilleure implémentation dans MySQL, car NULLc'est une valeur spéciale. La plupart des gens sont probablement intéressés par les NOT NULLvaleurs.

John Garreth
la source
3

Le problème n'est pas les valeurs NULL. C'est la sélectivité de l'indice. Dans votre exemple, la sélectivité de source, pop1est meilleure que la sélectivité de just pop1. Il couvre plus de conditions dans la whereclause, il est donc plus susceptible de réduire le nombre de visites de page.

Vous pouvez penser que réduire le nombre de lignes de 50% est suffisant, mais ce n'est vraiment pas le cas. L'avantage des index dans une whereclause est de réduire le nombre de pages lues. Si une page a, en moyenne, au moins un enregistrement avec une valeur non NULL, alors il n'y a aucun gain à utiliser l'index. Et, s'il y a 10 enregistrements par page, alors presque chaque page aura l'un de ces enregistrements.

Vous pourriez essayer un index sur (pop1, vt, source). L'optimiseur devrait prendre celui-là.

En fin de compte, cependant, si la whereclause perd des enregistrements - il n'y a pas de règle mais disons 20% - alors l'index n'aidera probablement pas. Une exception serait lorsque l'index contient toutes les colonnes nécessaires à la requête. Il peut ensuite satisfaire la requête sans faire apparaître la page de données de chaque enregistrement.

Et, si un indice est utilisé et que la sélectivité est élevée, les performances avec l'index peuvent être pires que les performances sans lui.

Gordon Linoff
la source
Je pense que c'est vraiment les gammes qui font la différence (voir ma réponse). Bien que je pense qu'il pourrait être mieux implémenté dans MySQL, car la plupart des gens s'intéressent aux NOT NULLcolonnes.