Чому 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)  

Існує індекс прізвища, але оптимізатор не використовує його.
І я також:

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. Немає стимулів використовувати індекс.

ПРОБЛЕМА №2

Подивіться на запит

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

Ви дали йому індекс, але Оптимізатор запитів перейняв це. Я вже бачив таку поведінку ( Як змусити ПРИЄДНАЙТЕ використовувати певний індекс у MySQL? )

Чому це повинно статися?

Без WHEREпункту Оптимізатор запитів говорить про себе таке:

  • Це таблиця InnoDB
  • Це індексований стовпчик
  • В індексі є рядок_id gen_clust_index (він же кластерний індекс)
  • Чому я повинен дивитись на індекс, коли
    • немає WHERE пункту?
    • Мені завжди доведеться відскакувати назад до столу?
  • Оскільки всі рядки таблиці InnoDB містяться в тих же 16К блоках, що і gen_clust_index, я замість цього проведу повну перевірку таблиці.

Оптимізатор запитів обрав шлях найменшого опору.

Вас чекає невеликий шок, але ось так: чи знаєте ви, що Оптимізатор запитів буде обробляти MyISAM зовсім інакше?

Ви, напевно, говорите HUH ???? ЯК ????

MyISAM зберігає дані у .MYDфайлі, а всі індекси у.MYI файлі.

Цей самий запит створить інший план ПОЯСНЕННЯ, оскільки індекс живе в іншому файлі від даних. Чому? Ось чому:

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

Як можна бути впевненим у цьому? Я перевірив цю робочу теорію щодо того, як використання іншого сховища генерує інший план ПОЯСНЕННЯ (іноді кращий): Чи повинен індекс охоплювати всі вибрані стовпці, щоб він міг бути використаний для ЗАМОВЛЕННЯ?


1
-1 @Rolando ця відповідь не менш точна, ніж правильна відповідь Майкла-sqlbot, але вона неправильна, наприклад, в посібнику сказано: "MySQL використовує індекси для цих операцій: (...) для сортування або групування таблиці, якщо сортування або групування проводиться за крайнім лівим префіксом корисного індексу (...) ". Також деякі інші заяви вашої посади є спірними. Я рекомендую вам видалити цю відповідь або переробити її.
чудо173

Ця відповідь не правильна. Індекс може все ще використовуватися, навіть якщо немає пункту WHERE, якщо це дозволяє уникнути сортування.
устриця

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/uk/innodb-restrictions.html


Цікава точка зору. Стовпець є, varchar(1000)але це перевищує максимально дозволений для індексу показник - ~ 750
Cratylus

8
Ця відповідь має бути прийнятою.
ypercubeᵀᴹ

1
@ypercube Ця відповідь точніша за мою. +1 за ваш коментар і +1 для цієї відповіді. Нехай це слід прийняти замість мене.
RolandoMySQLDBA

1
@Timo, це цікаве запитання ... яке я б запропонував опублікувати як нове запитання, тут, можливо, з посиланням на цю відповідь, для контексту. Дати повне виведення EXPLAIN SELECT ..., а також SHOW CREATE TABLE ...і з SELECT @@VERSION;тих пір зміни в оптимізатор в різних версіях можуть бути актуальними.
Майкл - sqlbot

1
На даний момент я можу повідомити, що (принаймні для 5.7) індекс префікса не допомагає з індексуванням нуля, як я просив у своєму коментарі вище.
Тимо

2

Я зробив відповідь, оскільки коментар не підтримує форматування, і RolandoMySQL DBA розповів про gen_clust_index та innodb. І це дуже важливо для таблиці, заснованої на innodb. Це йде далі, ніж звичайні знання DBA, оскільки вам потрібно вміти аналізувати код C.

ВЖЕ ЗАВЖДИ ЗАВЖДИ робіть ПЕРВІЙНИЙ КЛЮЧ або УНІКАЛЬНИЙ КЛЮЧ, якщо ви використовуєте Innodb. Якщо ви не innodb будете використовувати власний згенерований ROW_ID, який може принести вам більше шкоди, ніж користі.

Я спробую пояснити це легко, оскільки доказ базується на коді С.

/**********************************************************************//**
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-> mutex));

Цей рядок гарантує, що лише один потік може одночасно отримувати доступ до dict_sys-> mutex. Що робити, якщо значення вже було вимкнено ... так, нитка повинна зачекати, щоб ви отримали щось на зразок приємної випадкової функції, як блокування потоку, або якщо у вас є більше таблиць без власного ПЕРВИЧНОГО КЛЮЧА або УНІКАЛЬНОГО КЛЮЧА, тоді ви матимете гарну функцію з " Блокування таблиці " 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, для унікального індексу для просування до ПК.
Рік Джеймс

"модульні (%) обчислення повільні" - Більш важливим є те, який відсоток часу INSERTвідводиться на цю функцію. Я підозрюю, що це незначно. Контрактуйте зусилля на розгортання стовпців навколо, виконайте операції BTree, включаючи випадкові розбиття блоків, різні мутекси на buffer_pool, речі зміни буфера тощо
Рік Джеймс,

Правда @RickJames накладні витрати можуть бути дуже невеликими, але багато невеликих цифр також складаються (все одно буде мікрооптимізація) .. Окрім першої проблеми, яка найбільше викликає проблеми
Раймонд Ніджленд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.