MySQL первичный ключ не используется в запросе

0

У меня есть запрос MySQL, который, по-видимому, не использует один из первичных ключей, и это замедляет его.

Таблицы выглядят так:

staff_main:
    int staff_ID (the primary key)
    string name

production_role:
    int row_index (primary key, auto-incremented)
    int staff_ID (indexed)
    int production_ID (indexed)
    int role_ID

production_role_episodes:
    int row_index (primary key, autoincremented)
    int match_index (foreign key to production_role.row_index)
    int episode_index (foreign key to episode_info.episode_index)

episode_info:
    int episode_index (primary key)
    int production_ID
    ...other info not used here

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

SELECT staff_main.staff_ID AS sid,
    staff_main.name AS name   
FROM production_role_episodes      
    JOIN production_role ON (production_role.row_index = production_role_eps.match_index)      
JOIN staff_main USING (staff_ID)     
WHERE production_role_eps.episode_index = {episode}    
    AND production_role.role_ID = {role}     
ORDER BY name

Штатная таблица имеет ~ 9000 рядов, и это начало замедляться. EXPLAIN произвел следующее:

+----+-------------+---------------------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
| id | select_type | table               | type   | possible_keys    | key      | key_len | ref                                          | rows | Extra                           |
+----+-------------+---------------------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
|  1 | SIMPLE      | staff_main          | ALL    | PRIMARY          | NULL     | NULL    | NULL                                         | 9327 | Using temporary; Using filesort |
|  1 | SIMPLE      | production_role     | ref    | PRIMARY,staff_ID | staff_ID | 4       | test_prod_db.staff_main.staff_ID             |    2 | Using where                     |
|  1 | SIMPLE      | production_role_eps | eq_ref | PRIMARY          | PRIMARY  | 8       | test_prod_db.production_role.row_index,const |    1 | Using index                     |
+----+-------------+-------====----------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+

По-видимому, он не использует staff_main.staff_ID в качестве ключа, несмотря на то, что это первичный ключ. Я попытался заставить его, добавив ИНДЕКС ПОЛЬЗОВАТЕЛЯ (PRIMARY) в staff_main JOIN, но, согласно EXPLAIN, он все еще не использует ключ. Я попытался переставить JOINs, я попытался заменить USING (staff_ID) на ON (production_role.staff_ID = staff_main.staff_ID), без кубиков.

Может ли кто-нибудь сказать мне, что происходит? staff_main не собирается уменьшаться, поэтому этот запрос просто будет отставать все больше и больше, если я не могу использовать этот индекс.

  • 0
    Являются ли определения столбцов для staff_ID одинаковыми в обеих таблицах? Если отличается, MySQL не будет использовать ключ. Может ли сотрудник хранить более одной записи в production_role - если нет, то помогает ли staff_ID индекса staff_ID UNIQUE ? Что говорит EXPLAIN , если вы удалите ORDER BY ?
  • 0
    Staff_ID - это неподписанный INT в обоих местах. Штат может иметь несколько записей в таблице production_role (комбинация staff_ID, production_ID и role_ID уникальна, но любое подмножество этого не является). Я попробовал EXPLAIN без ORDER BY и получил тот же результат.
Теги:
query-performance

2 ответа

0
Лучший ответ

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

Вы можете указать подсказку о том, что сканирование таблицы очень дорогостоящее, чтобы исключить сканирование таблиц. Но есть вероятность, что оптимизатор прав, и выполнение запроса в другом направлении будет стоить вам больше.

Мне кажется, что вам нужны эти два индекса (role_ID не индексируется в вашем описании), с этой точной структурой:

CREATE INDEX production_role_ndx ON production_role(role_ID, row_index, staff_ID);

CREATE INDEX production_role_eps_ndx ON production_role_episodes(episode_index, match_index);

Вам, похоже, не нужно столько, для этого запроса (но, возможно, для других?), Эти другие:

int staff_ID (indexed)
int production_ID (indexed)

Более длинное объяснение

Ваш запрос (сокращенный):

SELECT staff_ID, name
FROM pre
JOIN pr ON (pr.row_index = pre.match_index)
JOIN sm ON (sm.staff_ID = pr.staff_ID)
WHERE pre.episode_index = {episode}    
AND pr.role_ID = {role}
ORDER BY name

Итак, что ему нужно? И откуда это удобнее начать?

Данные поступают из двух мест: индексы (получение их выполняется быстро) и таблицы (получение их происходит медленно).

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

Для выполнения вышеуказанного запроса требуется:

sm.staff_ID, sm name                                      for the SELECT
pr.row_index, pre.match_index, sm.staff_ID, pr.staff_ID   for the JOIN
pre.episode_index, pr.role_ID                             for the WHERE

Чтобы оптимально выполнить запрос, нам нужно как можно скорее сократить данные, поэтому нам нужно знать, больше ли индекс эпизода или мощность идентификатора роли. Скорее всего, роли несколько, а эпизодов много, а это означает, что ограничение одного эпизода из 1000 уменьшит наши данные на 1/1000, а фильтрация на роли уменьшится, может быть, на 1/20.

Поэтому мы запускаем запрос с WHERE только на pre.episode_index. И нам нужен индекс на pre, который имеет, как первое поле, episode_index. Pre - наша главная таблица.

Тогда мы присоединимся к pr. У нас также был фильтр на pr.role_ID. Как мы находим строки pr?

pr.row_index = pre.match_index
pr.role_ID = {role}

JOIN pr ON (pr.row_index = pre.match_index AND pr_role_ID = {role})

Поэтому мы хотим сначала проиндексировать pr на row_index, потому что он управляется из первой таблицы, а role_ID - второй, чтобы немедленно ограничить работу. Мы еще не получили доступ к одной из двух таблиц: мы проверили только индекс.

Если мы добавим третий столбец с идентификатором персонала в индекс pr, то данные, которые нам понадобятся в будущем, т.е. staff_ID, будут содержаться в индексе, который станет тем, что называется индексом покрытия, - и мы не будем нужна таблица pr вообще. Вы должны увидеть в EXPLAIN что-то вроде "используя буфер JOIN", что означает, что соединение происходит по частям в оптимизированных "всплесках".

Разумеется, оценка EXPLAIN будет по-прежнему основываться на количестве строк первого ГДЕ, поэтому это будет среднее число рядов эпизодов, умноженное на среднее число ролей. Это худшая оценка: вы хорошо знаете, что некоторые комбинации эпизода и роли могут фактически ничего не возвращать. Таким образом, вы не должны позволять огромной оценке беспокоиться о вас.

На данный момент у нас есть staff_main, и запрос поставляет staff_ID в качестве основного ключа, поэтому нам не нужно ничего делать: просто присоединитесь к staff_main. Для хорошей меры в select укажите, что staff_ID поступает из pr, а не staff_main. Значение такое же, и оно, вероятно, ничего не меняет, но доступ к pr.staff_ID гарантирован и прост (у нас он есть в индексе покрытия), и мы не хотим путать оптимизатор на всякий случай.

  • 0
    Я пытался добавить эти индексы. Это сделало это быстрее, но теперь это первая строка на EXPLAIN: | 1 | ПРОСТО | производственная роль | ВСЕ | PRIMARY, staff_ID, staff_role_index | NULL | NULL | NULL | 29290 | Используя где; Используя временные; Использование сортировки файлов | Он работает быстрее, но должен сканировать таблицу в три раза дольше?
  • 0
    Я пытался добавить своего рода объяснение. Вы можете проверить некоторые хорошие книги по SQL, такие как «Искусство SQL», или блог производительности Percona по индексированию. У меня были хорошие сообщения от людей, которые просто заменили MySQL на Percona MySQL.
0

Это production_role_episodes? Или production_role_eps? Я предполагаю, что это действительный рефакторинг запроса:

SELECT  sm.staff_ID AS sid, sm.name AS name
    FROM  production_role_episodes AS pre
    JOIN  production_role AS pr  ON (pr.row_index = pre.match_index)
    JOIN  staff_main AS sm USING (staff_ID)
    WHERE  pre.episode_index = {episode}
      AND  pr.role_ID = {role}
    ORDER BY  name

Я бы добавил эти индексы:

pre: (episode_index, match_index)
pr:  (role_ID, row_index, staff_ID)
sm:  (staff_id)  -- already the PK

Что касается того, почему ПК не используется, мне нужно увидеть типы данных (и другие вещи); пожалуйста, укажите SHOW CREATE TABLE.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню