Почему MySQL игнорирует индекс даже для силы для этого порядка?


14

Я запускаю EXPLAIN:

mysql> explain select last_name from employees order by last_name;
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  

Индексы в моей таблице:

mysql> show index from employees;  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| Table     | Non_unique | Key_name      | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| employees |          0 | PRIMARY       |            1 | subsidiary_id | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          0 | PRIMARY       |            2 | employee_id   | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          1 | idx_last_name |            1 | last_name     | A         |       10031 |      700 | NULL   |      | BTREE      |         |               |  
| employees |          1 | date_of_birth |            1 | date_of_birth | A         |       10031 |     NULL | NULL   | YES  | BTREE      |         |               |  
| employees |          1 | date_of_birth |            2 | subsidiary_id | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
5 rows in set (0.02 sec)  

На last_name есть индекс, но оптимизатор его не использует.
Итак, я делаю:

mysql> explain select last_name from employees force index(idx_last_name) order by last_name;  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  

Но все же индекс не используется! Что я здесь не так делаю?
Связано ли это с тем, что индекс есть NON_UNIQUE? Кстати, фамилияVARCHAR(1000)

Обновление запрошено @RolandoMySQLDBA

mysql> SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;  
+---------------+  
| DistinctCount |  
+---------------+  
|         10000 |  
+---------------+  
1 row in set (0.05 sec)  


mysql> SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;  
+----------+  
| COUNT(1) |  
+----------+  
|        0 |  
+----------+  
1 row in set (0.15 sec)  

Пожалуйста, запустите эти два запроса: 1) SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;2) SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;. Каков результат каждого подсчета?
— RolandoMySQLDBA

@RolandoMySQLDBA: я обновил ОП информацией, которую вы запрашивали.
— Cratylus

Еще два вопроса, пожалуйста: 1) SELECT COUNT(1) FullTableCount FROM employees;и 2) SELECT * FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A LIMIT 10;.
— RolandoMySQLDBA

Неважно, я вижу объяснение с тем, что мне нужно.
— RolandoMySQLDBA

2
@Cratylus вы приняли неправильный ответ, вы должны принять правильный ответ Михаэля sqlbot
— miracle173

Ответы:


6

ПРОБЛЕМА № 1

Посмотрите на запрос

select last_name from employees order by last_name;

Я не вижу значимого предложения WHERE, равно как и MySQL Query Optimizer. Нет стимула использовать индекс.

ПРОБЛЕМА № 2

Посмотрите на запрос

select last_name from employees force index(idx_last_name) order by last_name; 

Вы дали ему индекс, но Quit Opitmizer вступил во владение. Я видел это поведение раньше ( Как заставить JOIN использовать определенный индекс в MySQL? )

Почему это должно случиться?

Без WHEREпредложения Query Optimizer говорит следующее:

  • Это таблица InnoDB
  • Это индексированный столбец
  • Индекс имеет row_id gen_clust_index (он же Clustered Index)
  • Почему я должен смотреть на индекс, когда
    • нет WHEREпункта?
    • Я бы всегда приходил в норму к столу?
  • Поскольку все строки в таблице InnoDB находятся в тех же 16-килобайтных блоках, что и gen_clust_index, вместо этого я выполню полное сканирование таблицы.

Оптимизатор запросов выбрал путь наименьшего сопротивления.

Вас ожидает небольшой шок, но здесь все сказано: знаете ли вы, что Оптимизатор запросов будет обрабатывать MyISAM совершенно по-другому?

Вы, наверное, говорите ХАХ ???? КАК ????

MyISAM хранит данные в .MYDфайле и все индексы в .MYIфайле.

Тот же запрос создаст другой план EXPLAIN, поскольку индекс находится в файле, отличном от данных. Почему ? Вот почему:

  • Необходимые данные ( last_nameстолбец) уже упорядочены в.MYI
  • В худшем случае у вас будет полное сканирование индекса
  • Вы получите доступ только к столбцу last_nameиз индекса.
  • Вам не нужно просеивать ненужные
  • Вы не будете запускать создание временного файла для сортировки

Как можно быть в этом уверенным? Я проверил эту рабочую теорию о том, как использование другого хранилища приведет к созданию другого плана EXPLAIN (иногда лучшего): должен ли индекс охватывать все выбранные столбцы, чтобы его можно было использовать для ORDER BY?


1
-1 @Rolando этот ответ не менее точен, чем правильный ответ Michael-sqlbot, но он неправильный, например, руководство говорит: «MySQL использует индексы для этих операций: (...) Сортировать или сгруппировать таблицу, если сортировка или группировка выполняется по крайнему левому префиксу используемого индекса (...) ". Также некоторые другие утверждения вашего поста являются спорными. Я бы порекомендовал вам удалить этот ответ или переделать его.
— чудо173

Этот ответ не верен. Индекс может все еще использоваться, даже если нет предложения WHERE, если он избегает сортировки.
— oysteing

19

На самом деле проблема здесь в том, что это выглядит как префиксный индекс. Я не вижу определения таблицы в вопросе, но sub_part= 700? Вы не проиндексировали весь столбец, поэтому индекс не может использоваться для сортировки и также бесполезен в качестве индекса покрытия. Его можно использовать только для поиска строк, которые «могут» соответствоватьWHERE а серверному уровню (над механизмом хранения) придется дополнительно фильтровать сопоставленные строки. Вам действительно нужно 1000 символов для фамилии?


Обновление, чтобы проиллюстрировать: у меня есть тестовая таблица таблиц, содержащая более 500 строк, каждая с именем домена веб-сайта в столбце domain_name VARCHAR(254) NOT NULLи без индексов.

mysql> alter table keydemo add key(domain_name);
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

С индексированным полным столбцом запрос использует индекс:

mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table   | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | keydemo | index | NULL          | domain_name | 764     | NULL |  541 | Using index |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.01 sec)

Итак, теперь я отброшу этот индекс и просто индексирую первые 200 символов имени_домена.

mysql> alter table keydemo drop key domain_name;
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table keydemo add key(domain_name(200));
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | keydemo | ALL  | NULL          | NULL | NULL    | NULL |  541 | Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

mysql>

Вуаля.

Также обратите внимание, что индекс длиной 200 символов длиннее самого длинного значения в столбце ...

mysql> select max(length(domain_name)) from keydemo;
+--------------------------+
| max(length(domain_name)) |
+--------------------------+
|                       43 |
+--------------------------+
1 row in set (0.04 sec)

... но это не имеет никакого значения. Индекс, объявленный с длиной префикса, может использоваться только для поиска, но не для сортировки и не в качестве индекса покрытия, поскольку по определению он не содержит полного значения столбца.

Кроме того, вышеупомянутые запросы были выполнены для таблицы InnoDB, но их выполнение в таблице MyISAM дает практически идентичные результаты. Только разница в данном случае является то , что InnoDB рассчитывать наrows немного смещено (541) , тогда как MyISAM показывает точное число строк (563) , который является нормальным поведением , так как два двигателя для хранения ручки индекса погружения очень по- разному.

Я по-прежнему утверждаю, что столбец last_name, вероятно, больше, чем необходимо, но все же можно проиндексировать весь столбец, если вы используете InnoDB и работаете с MySQL 5.5 или 5.6:

По умолчанию ключ индекса для индекса из одного столбца может содержать до 767 байтов. То же ограничение длины применяется к любому префиксу ключа индекса. См. Раздел 13.1.13 « CREATE INDEXСинтаксис». Например, вы можете достичь этого предела с индексом префикса столбца более 255 символов для столбца TEXTили VARCHAR, предполагая UTF-8набор символов и максимум 3 байта для каждого символа. Когда innodb_large_prefixопция конфигурации включена, это ограничение длины увеличивается до 3072 байта для InnoDBтаблиц, использующих форматы строк DYNAMICи COMPRESSED.

- http://dev.mysql.com/doc/refman/5.5/en/innodb-restrictions.html


Интересная точка зрения. Столбец есть, varchar(1000)но он превышает максимально допустимый для индекса, который составляет ~ 750
— Cratylus

8
Этот ответ должен быть принятым.
— ypercubeᵀᴹ

1
@ypercube Этот ответ точнее моего. +1 за ваш комментарий и +1 за этот ответ. Пусть это будет принято вместо меня.
— RolandoMySQLDBA

1
@ Тимо, это интересный вопрос ... который я бы предложил опубликовать как новый вопрос, здесь, возможно, со ссылкой на этот ответ, для контекста. Опубликовать полный вывод EXPLAIN SELECT ..., а также SHOW CREATE TABLE ...и SELECT @@VERSION;так как изменения в оптимизаторе в разных версиях могут иметь значение.
— Майкл - sqlbot

1
К настоящему времени я могу сообщить, что (по крайней мере, для 5.7) индекс префикса не помогает с индексированием нуля, как я просил в моем комментарии выше.
— Тимо

2

Я ответил об этом, потому что комментарий не будет поддерживать форматирование, и администратор RolandoMySQL рассказал о gen_clust_index и innodb. И это очень важно для таблицы на основе innodb. Это идет дальше, чем обычные знания DBA, потому что вам нужно уметь анализировать код на C ..

Вы должны ВСЕГДА делать ПЕРВИЧНЫЙ КЛЮЧ или УНИКАЛЬНЫЙ КЛЮЧ, если вы используете Innodb. Если вы не используете innodb, вы будете использовать собственный сгенерированный ROW_ID, который может принести вам больше вреда, чем пользы.

Я постараюсь объяснить это легко, потому что доказательство основано на C-коде.

/**********************************************************************//**
Returns a new row id.
@return the new id */
UNIV_INLINE
row_id_t
dict_sys_get_new_row_id(void)
/*=========================*/
{
    row_id_t    id;

    mutex_enter(&(dict_sys->mutex));

    id = dict_sys->row_id;

    if (0 == (id % DICT_HDR_ROW_ID_WRITE_MARGIN)) {
          dict_hdr_flush_row_id();
    }

    dict_sys->row_id++;
    mutex_exit(&(dict_sys->mutex));
    return(id);
}

Первая проблема

mutex_enter (& (dict_sys-> мьютекс));

Эта строка гарантирует, что только один поток может одновременно обращаться к dict_sys-> mutex. Что если уже значение было взаимно изменено ... да, поток должен ждать, чтобы вы получили что-то вроде хорошей случайной функции, такой как блокировка потока, или если у вас есть больше таблиц без вашего собственного PRIMARY KEY или UNIQUE KEY, тогда у вас будет хорошая функция с innodb ' блокировка таблицы ' - это не причина, по которой MyISAM был заменен на InnoDB из-за замечательной функции, называемой блокировкой на основе записей / строк.

Вторая проблема

(0 == (id% DICT_HDR_ROW_ID_WRITE_MARGIN))

Вычисления по модулю (%) медленные, не очень хорошие, если вы выполняете пакетную вставку, потому что их нужно каждый раз пересчитывать ... и поскольку DICT_HDR_ROW_ID_WRITE_MARGIN (значение 256) представляет собой степень двойки, это можно сделать намного быстрее ..

(0 == (id & (DICT_HDR_ROW_ID_WRITE_MARGIN - 1)))

Примечание: если компилятор C настроен для оптимизации и является хорошим оптимизатором, оптимизатор C исправит «тяжелый» код в более легкой версии

Девиз истории - всегда создавайте свой ПЕРВИЧНЫЙ КЛЮЧ или убедитесь, что у вас есть УНИКАЛЬНЫЙ индекс, когда вы создаете таблицу с самого начала


Добавьте репликацию на основе строк и тот факт, что идентификаторы строк не одинаковы для разных серверов, и точка зрения Рэймонда о создании первичного ключа еще более важна.

Пожалуйста, не предполагайте, что этого UNIQUEдостаточно - необходимо также включить только столбцы, отличные от NULL, для уникального индекса, который будет повышен до PK.
— Рик Джеймс

«По модулю (%) вычисления выполняются медленно» - более важно то, какой процент времени INSERTтратится на эту функцию. Я подозреваю, это незначительно. Сравните усилия по смещению столбцов, выполняйте операции BTree, включая случайное разбиение на блоки, различные мьютексы в buffer_pool, изменения в буфере и т. Д.
— Рик Джеймс

Правда @RickJames, издержки могут быть очень малыми, но также складывается много маленьких чисел (все равно это будет микрооптимизация). Кроме того, первая проблема - это самая большая проблема
— Raymond Nijland
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.