Почему 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?

RolandoMySQLDBA
источник
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

Майкл - sqlbot
источник
Интересная точка зрения. Столбец есть, 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