Как разработать индексы для столбцов со значениями NULL в MySQL?

11

У меня есть база данных с 40 миллионами записей, и я хочу выполнить запросы со следующим WHEREпунктом

...
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столбец с плавающей точкой, который также может быть NULL POP1 IS NOT NULLследует исключить около 50% записей, поэтому я поставил его в начале. Все остальные условия уменьшают количество только незначительно.

Среди прочего я разработал индекс pop1_vt_source, который, кажется, не используется, в то время как используется индекс с vtпервым столбцом. 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 |

Почему индекс с pop1первым столбцом не используется? Из-за NOTили из-за NULLв целом. Как я могу улучшить дизайн моих индексов и предложений WHERE? Даже при ограничении до 10 записей запрос занимает более 30 секунд, хотя первые 100 записей в таблице должны содержать 10 совпадений.

Sven
источник

Ответы:

10

Это 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`;

дает:

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

Создайте индекс:

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

SHOW INDEX FROM `myTab`;

дает:

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

Теперь объясните выбор. Кажется, что MySQL использует индекс, даже если вы используете 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 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Но, сравнивая NOT NULLи NULL, кажется, что MySQL предпочитает другие индексы при использовании NOT NULL. Хотя это, очевидно, не добавляет никакой информации. Это потому, что MySQL интерпретируется NOT NULLкак диапазон, как вы можете видеть в type-column. Я не уверен, если есть обходной путь:

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

Я думаю, что в MySQL может быть лучшая реализация, потому что NULLэто особая ценность. Вероятно, большинство людей интересуются NOT NULLценностями.

Джон Гаррет
источник
3

Проблема не в значениях NULL. Это селективность индекса. В вашем примере избирательность source, pop1лучше, чем избирательность справедливости pop1. Он охватывает больше условий в whereпредложении, поэтому он с большей вероятностью сократит количество посещений страниц.

Вы можете подумать, что достаточно уменьшить количество строк на 50%, но на самом деле это не так. Преимущество индексов в whereпредложении заключается в уменьшении количества читаемых страниц. Если на странице в среднем имеется хотя бы одна запись со значением, отличным от NULL, то использование индекса не дает никакой выгоды. И, если на странице 10 записей, то почти на каждой странице будет одна из этих записей.

Вы можете попробовать индекс на (pop1, vt, source). Оптимизатор должен выбрать это.

Однако, в конце концов, если whereпредложение сохраняет потерянные записи - правила не существует, скажем, 20% - тогда индекс, вероятно, не поможет. Единственное исключение будет, когда индекс содержит все столбцы, необходимые для запроса. Затем он может удовлетворить запрос, не вводя страницу данных для каждой записи.

И если индекс используется и селективность высока, то производительность с индексом может быть хуже, чем производительность без него.

Гордон Линофф
источник
Я думаю, что это действительно диапазоны, которые вызывают разницу (см. Мой ответ). Хотя я думаю, что это может быть лучше реализовано в MySQL, так как большинство людей интересуются NOT NULLстолбцами.