#mysql #sql #select #sql-order-by
#mysql #sql #выберите #sql-order-by
Вопрос:
У меня есть следующий запрос MySQL, который занимает около 40 секунд на виртуальной машине Linux:
SELECT
* FROM `clients_event_log`
WHERE
`ex_long` = 1475461 AND
`type` in (2, 1) AND NOT
(
(category=1 AND error=-2147212542) OR
(category=7 AND error=67)
)
ORDER BY `ev_time` DESC LIMIT 100
Таблица содержит около 7 миллионов строк, размером около 800 МБ, и в ней есть индексы для всех полей, используемых в предложениях WHERE и ORDER BY .
Теперь, если я изменю запрос таким образом, чтобы порядок выполнялся во внешнем выборе, все работает намного быстрее (около 100 мс):
SELECT res.* FROM
(
SELECT * FROM `clients_event_log`
WHERE
`ex_long` = 1475461 AND
`type` in (2, 1) AND NOT
(
(category=1 AND error=-2147212542) OR
(category=7 AND error=67)
)
) AS res
ORDER BY res.ev_time DESC LIMIT 0, 100
У вас есть какие-либо идеи, почему первый запрос занимает так много времени? Спасибо.
Более позднее обновление:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE clients_event_log index category,ex_long,type,error,categ_error ev_time 4 NULL 5636 Using where
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY <derived2> system NULL NULL NULL NULL 1
2 DERIVED clients_event_log ref category,ex_long,type,error,categ_error ex_long 5 131264 Using where
Определение таблицы:
CREATE TABLE `clients_event_log` (
`ev_id` int(11) NOT NULL,
`type` int(6) NOT NULL,
`ev_time` int(11) NOT NULL,
`category` smallint(6) NOT NULL,
`error` int(11) NOT NULL,
`ev_text` varchar(1024) DEFAULT NULL,
`userid` varchar(20) DEFAULT NULL,
`ex_long` int(11) DEFAULT NULL,
`client_ex_long` int(11) DEFAULT NULL,
`ex_text` varchar(1024) DEFAULT NULL,
PRIMARY KEY (`ev_id`),
KEY `category` (`category`),
KEY `ex_long` (`ex_long`),
KEY `type` (`type`),
KEY `ev_time` (`ev_time`),
KEY `error` (`error`),
KEY `categ_error` (`category`,`error`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Комментарии:
1. Конечно, этого не должно быть. Запросы семантически равны и должны приводить к одному и тому же плану выполнения. Поскольку это, очевидно, не так, я бы счел это недостатком СУБД.
2. Вероятно, у вас есть отдельные индексы для всех полей, например, один индекс включен
ex_long
, один индекс включенtype
, один индекс включенev_time
. MySQL может использовать только один индекс для каждого запроса и, вероятно, использует неправильный в вашем 1-м запросе (например, индекс включенev_time
). Посмотрите наexplain
вывод, чтобы проверить это (и добавьте его в свой вопрос). Итак, чтобы решить вашу проблему, вам понадобится составной индекс, напримерex_long, ev_time
(в зависимости от ваших данных, возможно, и других столбцов) или принудительно ввести правильный одностолбцовый индекс (тот, который, вероятно, использовался в вашем 2-м запросе).3. Первый план выполнения просто глуп (доступ к каждой записи через индекс только с целью их чтения в порядке окончательной сортировки.) Как я уже сказал: недостаток в оптимизаторе MySQL.
4. @ThorstenKettner Вы правы, это не должно иметь значения, но в MySQL это имеет значение. В старых версиях mysql так создавалась производная таблица, и, следовательно, MyQSL не будет использовать now outer
order by
-index во внутреннем запросе. 5.7 правильно выполнит его.5. @Julian Ваш
explain
показал, как и ожидалось, что вы используете разные индексы в своих запросах. Вы можете либо добавить составной индекс (с таким количеством столбцов, сколько захотите, но, я думаю, 2 должно быть хорошо), либо (или, если он по-прежнему не работает по какой-то причине), вы можете принудительно ввести правильный индекс, используяfrom `clients_event_log` force index (ex_long)
в своем первом запросе.
Ответ №1:
В итоге я использовал второй запрос (внутренний SELECT), потому что оптимизатор MySQL решил всегда использовать ev_time
индекс, даже если я пробовал несколько версий составного индекса, содержащего столбцы в предложениях WHERE и ORDER BY .
Использование force index (ex_long)
также сработало.
Версия MySQL была 5.5.38
Спасибо.
Комментарии:
1. Просто имейте в виду, что при обновлении до версии 5.7 этот код может больше не работать. Ваш
limit
допускает возможность того, что mysql выбирает индекс onev_time
, поэтому он может сделать это снова, как только производная таблица больше не будет создаваться с этим синтаксисом, поэтомуforce index
(либо для составного, либо для отдельного индекса) будет более безопасным выбором. Или попробуйтеoptimize
свою таблицу, ваша статистика может быть отключена.
Ответ №2:
Добавьте эти
INDEX(ev_long, ev_time),
INDEX(ev_long, type)
и используйте первый формат запроса, и пусть оптимизатор решит, что лучше, основываясь на статистике.