Jak zaprojektować indeksy dla kolumn z wartościami NULL w MySQL?

11

Mam bazę danych z 40 milionami wpisów i chcę uruchamiać zapytania z następującą WHEREklauzulą

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

POP1jest zmiennoprzecinkową kolumną, która może również mieć wartość NULL. POP1 IS NOT NULLpowinienem wykluczyć około 50% wpisów, dlatego umieszczam to na początku. Wszystkie pozostałe warunki zmniejszają liczbę tylko nieznacznie.

Między innymi zaprojektowałem indeks pop1_vt_source, który wydaje się nieużywany, podczas gdy indeks z vtpierwszą kolumną jest używany. WYJAŚNIJ wyjście:

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

Dlaczego indeks z pop1jako pierwszą kolumną nie jest używany? Z powodu NOTlub z powodu NULLogólnie. Jak mogę poprawić projekt moich indeksów i klauzul GDZIE? Nawet przy ograniczeniu do 10 wpisów zapytanie trwa dłużej niż 30 sekund, chociaż pierwsze 100 wpisów w tabeli powinno zawierać 10 dopasowań.

Sven
źródło

Odpowiedzi:

10

To jest 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`;

daje:

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

Utwórz indeks:

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

SHOW INDEX FROM `myTab`;

daje:

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

teraz wyjaśnij wybrane. Wygląda na to, że MySQL korzysta z indeksu, nawet jeśli używasz 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 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Ale porównując NOT NULLi NULLwydaje się, że MySQL preferuje inne indeksy podczas używania NOT NULL. Chociaż to oczywiście nie dodaje żadnych informacji. Jest tak, ponieważ MySQL interpretuje NOT NULLjako zakres, jak widać w kolumnie typu. Nie jestem pewien, czy istnieje obejście:

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

Myślę, że może być lepsza implementacja w MySQL, ponieważ NULLjest to specjalna wartość. Prawdopodobnie większość ludzi interesuje się NOT NULLwartościami.

John Garreth
źródło
3

Problemem nie są wartości NULL. Jest to selektywność indeksu. W twoim przykładzie selektywność source, pop1jest lepsza niż selektywność just pop1. Obejmuje więcej warunków w whereklauzuli, więc jest bardziej prawdopodobne, że zmniejszy liczbę odwiedzin strony.

Możesz pomyśleć, że wystarczy zmniejszyć liczbę wierszy o 50%, ale tak naprawdę nie jest. Zaletą indeksów w whereklauzuli jest zmniejszenie liczby czytanych stron. Jeśli strona ma średnio co najmniej jeden rekord o wartości innej niż NULL, użycie indeksu nie przynosi korzyści. A jeśli na stronie jest 10 rekordów, prawie każda strona będzie miała jeden z tych rekordów.

Możesz wypróbować indeks (pop1, vt, source). Optymalizator powinien to wybrać.

Ostatecznie jednak, jeśli whereklauzula gubi zapisy - nie ma reguły, ale powiedzmy 20% - to indeks prawdopodobnie nie pomoże. Jedynym wyjątkiem może być sytuacja, gdy indeks zawiera wszystkie kolumny potrzebne w zapytaniu. Następnie może spełnić zapytanie bez wyświetlania strony danych dla każdego rekordu.

A jeśli indeks zostanie wykorzystany, a selektywność wysoka, wówczas wydajność z indeksem może być gorsza niż wydajność bez niego.

Gordon Linoff
źródło
Myślę, że to naprawdę zakresy powodują różnicę (patrz moja odpowiedź). Chociaż myślę, że można go lepiej zaimplementować w MySQL, ponieważ większość ludzi interesuje się NOT NULLkolumnami.