Оптимизация запроса MySQL, когда количество возвращаемых строк очень велико - PullRequest
0 голосов
/ 20 марта 2020

Контекст: у нас есть веб-сайт, где пользователи (продавцы) могут добавлять свои приложения / веб-сайты в систему и платить своим пользователям через API. Теперь проблема возникает, когда мы должны показать список этих транзакций продавцу на его панели инструментов. Каждый продавец генерирует сотни транзакций в секунду, и в среднем торговцы совершают около 2 миллионов транзакций в день, и на панели инструментов мы должны показывать продавцу сегодняшнюю статистику.

Основная проблема: мы должны показывать сегодняшние транзакции продавец, который составляет около 2 миллионов записей для одного продавца. Таким образом, запрос, подобный этому,

SELECT * FROM transactions WHERE user_id = 123 LIMIT 0,15

В нашем примере проверено 2 миллиона строк, и их никак нельзя уменьшить. Я думаю, что ограничение здесь не помогает, потому что MySQL по-прежнему проверяет все строки, а затем выбирает первые 15 из набора результатов.

Как мы можем оптимизировать подобные запросы, когда мы должны показывать миллионы записей (с нумерацией страниц) пользователю?

Редактировать:

Объяснить вывод:

enter image description here

Запрос:

explain select a.id, a.user_app_id, a.created_at, a.type, a.amount, a.currency_id, b.name, b.url from transactions as a left join user_apps as b on a.user_app_id = b.id where a.sender_user_id = ? and a.created_at BETWEEN '2020-03-20' AND '2020-03-21' order by a.created_at desc limit 15 offset 0

Подробности:

Индекс sender_user_id_2 - это составной индекс из столбцов sender_user_id(int) и created_at(timestamp).

Этот запрос занимает от 5 до 15 секунд, чтобы вернуть 15 строк.

Если я выполню тот же запрос для sender_user_id, который имеет только 24 транзакции в таблице, то ответ мгновенный.

1 Ответ

0 голосов
/ 22 марта 2020

Во-первых, давайте исправим то, что может быть ошибкой: вы включаете две ночи в этот "день". 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.

...