web-dev-qa-db-fra.com

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

J'ai une base de données avec 40 millions d'entrées et je souhaite exécuter des requêtes avec la clause WHERE 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 ;

POP1 est une colonne flottante qui peut également être NULL. POP1 IS NOT NULL devrait 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 vt comme 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 pop1 comme première colonne non utilisée? À cause de NOT ou à cause de NULL en 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.

11
Sven

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 NULL et NULL, il semble que MySQL préfère les autres index lors de l'utilisation de NOT NULL. Bien que cela n'ajoute évidemment aucune information. En effet, MySQL interprète NOT NULL comme 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 NULL est une valeur spéciale. La plupart des gens sont probablement intéressés par NOT NULL valeurs.

10
John Garreth

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, pop1 est mieux que la sélectivité de seulement pop1. Il couvre plusieurs des conditions de la clause where, il est donc plus probable 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 clause where 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 pouvez essayer un index sur (pop1, vt, source). L'optimiseur devrait prendre celui-là.

En fin de compte, cependant, si la clause where conserve des enregistrements perdus - 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 tous les colonnes nécessaires à la requête. Ensuite, il peut satisfaire la requête sans faire apparaître la page de données de chaque enregistrement.

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

3
Gordon Linoff