Во-первых, давайте исправим то, что может быть ошибкой: вы включаете две ночи в этот "день". BETWEEN
- это «включительно».
AND a.created_at BETWEEN '2020-03-20' AND '2020-03-21'
->
AND a.created_at >= '2020-03-20'
AND a.created_at < '2020-03-20' + INTERVAL 1 DAY
(Производительность не меняется, только устранение полуночи завтрашнего дня.)
В вашем простом запросе будут затронуты только 15 строк из-за LIMIT
. Однако для более сложных запросов может потребоваться собрать все строки, отсортировать их и только после этого очистить 15 строк. Техника предотвращения этой неэффективности выглядит примерно так: разработайте, если возможно, INDEX
, который обрабатывает все потребности WHERE
и ORDER BY
.
where a.sender_user_id = ?
AND a.created_at >= '2020-03-20'
AND a.created_at < '2020-03-20' + INTERVAL 1 DAY
order by a.created_at desc
INDEX(sender_user_id, created_at)
- в таком порядке. (И в вашем запросе больше ничего не посягает на это.)
Разбиение по страницам через OFFSET
создает еще одну проблему с производительностью - она должна перешагнуть все строки OFFSET
, прежде чем получить те, которые вы хотите. Это можно решить, если вспомнить, где вы остановились .
Итак, почему EXPLAIN
думает, что оно достигнет миллиона строк? Потому что Explain глуп, когда дело доходит до обработки LIMIT
. Существует лучший способ оценить усилия. Это покажет 15, а не миллион, если все будет хорошо. Для LIMIT 150, 15
будет отображаться 165.
Вы сказали, что «Индекс sender_user_id_2 является составным индексом столбцов sender_user_id (int) и creation_at (timestamp)». Можете ли вы предоставить SHOW CREATE TABLE
, чтобы мы могли проверить, не происходит ли что-то еще?
Хммм ... Интересно, следует ли изменить
order by a.created_at desc
в соответствии с индексом:
order by a.sender_user_id DESC, a.created_at desc
(Какую версию MySQL вы используете? Я провел некоторые эксперименты и не обнаружил разницы из-за наличия (или нет) sender_user_id
в `ORDER BY.)
(Проблема - Кажется, что JOIN
препятствует эффективному использованию LIMIT
. Все еще копает ...)
Новое предложение:
select a.id, a.user_app_id, a.created_at, a.type, a.amount, a.currency_id,
b.name, b.url
from
(
SELECT a1.id
FROM transactions as a1
where a1.sender_user_id = ?
AND a.created_at >= '2020-03-20'
AND a.created_at < '2020-03-20' + INTERVAL 1 DAY
order by a1.created_at desc
limit 15 offset 0
) AS x
JOIN transactions AS a USING(id)
left join user_apps as b ON x.user_app_id = b.id
Это использует обобщенный c трюк для перемещения LIMIT
в производную таблицу с минимальным количеством других вещей. Затем, имея только 15 идентификаторов, JOINs
для других таблиц становится «быстрым».
В моем эксперименте (с другой парой таблиц) он затронул только 5 * 15 строк. Я проверил несколько версий; похоже, все нуждаются в этой технике. Раньше он Handler_reads
проверял результаты.
Когда я пытался использовать JOIN
, но не производную таблицу, он касался 2 * N строк, где N - количество строк без LIMIT
.