¿Cómo diseñar índices para columnas con valores NULL en MySQL?

11

Tengo una base de datos con 40 millones de entradas y quiero ejecutar consultas con la siguiente 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 ;

POP1es una columna flotante que también puede ser NULL. POP1 IS NOT NULLdebería excluir aproximadamente el 50% de las entradas, es por eso que lo puse al principio. Todos los demás términos reducen el número solo marginalmente.

Entre otros, diseñé un índice pop1_vt_source, que parece no usarse, mientras que se usa un índice con la vtprimera columna. EXPLICAR-salida:

| 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 qué pop1no se usa el índice como primera columna? Por el NOTo por el NULLen general. ¿Cómo puedo mejorar el diseño de mis índices y las cláusulas WHERE? Incluso cuando se limita a 10 entradas, la consulta lleva más de 30 segundos, aunque las primeras 100 entradas en la tabla deben contener las 10 coincidencias.

Sven
fuente

Respuestas:

10

Es el 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`;

da:

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

Crea el índice:

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

SHOW INDEX FROM `myTab`;

da:

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

Ahora explique las selecciones. Parece que MySQL usa el índice, incluso si usa 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 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Pero, al comparar NOT NULLy NULL, parece que MySQL prefiere otros índices al usar NOT NULL. Aunque esto obviamente no agrega ninguna información. Esto se debe a que MySQL interpreta NOT NULLcomo un rango como puede ver en la columna de tipo. No estoy seguro si hay una solución 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 |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+

Creo que podría haber una mejor implementación en MySQL, porque NULLes un valor especial. Probablemente la mayoría de las personas están interesadas en los NOT NULLvalores.

John Garreth
fuente
3

El problema no son los valores NULL. Es la selectividad del índice. En su ejemplo, la selectividad de source, pop1es mejor que la selectividad de just pop1. Cubre más de las condiciones de la wherecláusula, por lo que es más probable que reduzca las visitas a la página.

Puede pensar que reducir el número de filas en un 50% es suficiente, pero realmente no lo es. El beneficio de los índices en una wherecláusula es reducir el número de páginas que se leen. Si una página tiene, en promedio, al menos un registro con un valor no NULL, entonces no hay ganancia por usar el índice. Y, si hay 10 registros por página, casi todas las páginas tendrán uno de esos registros.

Puede probar un índice (pop1, vt, source). El optimizador debería recogerlo.

Al final, sin embargo, si la wherecláusula mantiene la pérdida de registros (no hay una regla pero digamos 20%), entonces el índice probablemente no ayudará. Una excepción sería cuando el índice contiene todas las columnas que necesita la consulta. Entonces puede satisfacer la consulta sin traer la página de datos para cada registro.

Y, si se utiliza un índice y la selectividad es alta, entonces el rendimiento con el índice podría ser peor que el rendimiento sin él.

Gordon Linoff
fuente
Creo que realmente son los rangos los que causan la diferencia (vea mi respuesta). Aunque creo que podría implementarse mejor en MySQL, ya que la mayoría de las personas están interesadas en las NOT NULLcolumnas.