У меня есть запрос 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 не собирается уменьшаться, поэтому этот запрос просто будет отставать все больше и больше, если я не могу использовать этот индекс.
Оптимизатор сообщает 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 гарантирован и прост (у нас он есть в индексе покрытия), и мы не хотим путать оптимизатор на всякий случай.
Это 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
.
staff_ID
одинаковыми в обеих таблицах? Если отличается, MySQL не будет использовать ключ. Может ли сотрудник хранить более одной записи вproduction_role
- если нет, то помогает лиstaff_ID
индексаstaff_ID
UNIQUE
? Что говоритEXPLAIN
, если вы удалитеORDER BY
?