Como criar índices para colunas com valores NULL no MySQL?

11

Eu tenho um banco de dados com 40 milhões de entradas e quero executar consultas com a seguinte WHEREcláusula

...
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é uma coluna flutuante que também pode ser NULL. POP1 IS NOT NULLdeve excluir cerca de 50% das entradas, é por isso que coloco no início. Todos os outros termos reduzem o número apenas marginalmente.

Entre outros, projetei um índice pop1_vt_source, que parece não ser usado, enquanto um índice com a vtprimeira coluna é usado. Saída 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 |

Por que o índice com pop1a primeira coluna não é usado? Por causa do NOTou por causa do NULLem geral. Como posso melhorar o design dos meus índices e cláusulas WHERE? Mesmo ao limitar a 10 entradas, a consulta leva mais de 30 segundos, embora as 100 primeiras entradas da tabela devam conter as 10 correspondências.

Sven
fonte

Respostas:

10

É o 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`;

dá:

+--------+------+
| 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 |
+--------+------+

Crie o índice:

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

SHOW INDEX FROM `myTab`;

dá:

+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| 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      |         |               |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

Agora explique as seleções. Parece que o MySQL usa o índice, mesmo se você usar 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 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Mas, ao comparar NOT NULLe NULL, parece que o MySQL prefere outros índices ao usar NOT NULL. Embora isso obviamente não adicione nenhuma informação. Isso ocorre porque o MySQL interpreta NOT NULLcomo um intervalo, como você pode ver na coluna type. Não tenho certeza se existe uma solução alternativa:

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 |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+

Eu acho que poderia haver uma melhor implementação no MySQL, porque NULLé um valor especial. Provavelmente a maioria das pessoas está interessada em NOT NULLvalores.

John Garreth
fonte
3

O problema não é dos valores NULL. É a seletividade do índice. No seu exemplo, a seletividade de source, pop1é melhor que a seletividade de just pop1. Ele abrange mais condições da wherecláusula, portanto, é mais provável que você reduza as ocorrências de páginas.

Você pode pensar que reduzir o número de linhas em 50% é suficiente, mas na verdade não é. O benefício dos índices em uma wherecláusula é reduzir o número de páginas que estão sendo lidas. Se uma página tiver, em média, pelo menos um registro com um valor não NULL, não haverá ganho em usar o índice. E, se houver 10 registros por página, quase todas as páginas terão um desses registros.

Você pode tentar um índice (pop1, vt, source). O otimizador deve escolher esse.

No final, porém, se a wherecláusula estiver perdendo registros - não há regra, mas digamos 20% -, o índice provavelmente não ajudará. Uma exceção seria quando o índice contiver todas as colunas necessárias à consulta. Em seguida, ele pode satisfazer a consulta sem trazer a página de dados para cada registro.

E, se um índice for usado e a seletividade for alta, o desempenho com o índice poderá ser pior do que o desempenho sem ele.

Gordon Linoff
fonte
Eu acho que realmente são os intervalos que causam a diferença (veja minha resposta). Embora eu ache que poderia ser melhor implementado no MySQL, já que a maioria das pessoas está interessada em NOT NULLcolunas.