Come progettare indici per colonne con valori NULL in MySQL?


11

Ho un database con 40 milioni di voci e voglio eseguire query con la seguente WHEREclausola

...
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è una colonna float che può anche essere NULL. POP1 IS NOT NULLdovrebbe escludere circa il 50% delle voci, ecco perché l'ho messo all'inizio. Tutti gli altri termini riducono il numero solo marginalmente.

Tra gli altri, ho progettato un indice pop1_vt_source, che sembra non essere utilizzato, mentre viene utilizzato un indice con vtla prima colonna. SPIEGARE-uscita:

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

Perché l'indice con pop1come prima colonna non viene utilizzato? A causa NOTo a causa di NULLin generale. Come posso migliorare il design dei miei indici e le clausole WHERE? Anche quando si limita a 10 voci, la query impiega più di 30 secondi, sebbene le prime 100 voci nella tabella dovrebbero contenere le 10 corrispondenze.

Risposte:


10

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

Crea l'indice:

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

ora spiega le selezioni. Sembra che MySQL utilizzi l'indice, anche se usi 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 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Ma, confrontando NOT NULLe NULL, sembra che MySQL preferisca altri indici durante l'utilizzo NOT NULL. Anche se questo ovviamente non aggiunge alcuna informazione. Questo perché MySQL interpreta NOT NULLun intervallo come puoi vedere nella colonna del tipo. Non sono sicuro Se esiste una soluzione 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 |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+

Penso che ci potrebbe essere una migliore implementazione in MySQL, perché ha NULLun valore speciale. Probabilmente molte persone sono interessate ai NOT NULLvalori.


3

Il problema non sono i valori NULL. È la selettività dell'indice. Nel tuo esempio, la selettività di source, pop1è migliore della selettività di just pop1. Copre più delle condizioni nella whereclausola, quindi è più probabile che riduca i risultati della pagina.

Potresti pensare che ridurre il numero di righe del 50% sia sufficiente, ma in realtà non lo è. Il vantaggio degli indici in una whereclausola è di ridurre il numero di pagine da leggere. Se una pagina ha, in media, almeno un record con un valore diverso da NULL, non è possibile utilizzare l'indice. E, se ci sono 10 record per pagina, quasi ogni pagina avrà uno di quei record.

Potresti provare un indice su (pop1, vt, source). L'ottimizzatore dovrebbe selezionarlo.

Alla fine, però, se la whereclausola si sta perdendo di record - non c'è regola ma diciamo il 20% - probabilmente l'indice non sarà di aiuto. Un'eccezione sarebbe quando l'indice contiene tutte le colonne necessarie alla query. Quindi può soddisfare la query senza portare la pagina di dati per ciascun record.

E, se viene utilizzato un indice e la selettività è elevata, le prestazioni con l'indice potrebbero essere peggiori delle prestazioni senza di esso.


Penso che siano proprio gli intervalli che causano la differenza (vedi la mia risposta). Anche se penso che potrebbe essere meglio implementato in MySQL, dal momento che la maggior parte delle persone sono interessate alle NOT NULLcolonne.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.