Pull to refresh

Ускорение sql запросов к большим таблицам. Оптимизация пагинации

Reading time4 min
Views17K

При  умеренных объёмах базы данных в использовании offset нет ничего плохого, но со временем база данных растёт и запросы начинают «тормозить». Становится актуальным ускорение запросов.

Очевидно, если причина в росте объёмов базы данных, то используя главный принцип дзюдо «падающего - толкни, нападающего - тяни»,  следует ещё увеличить объём, в данном случае путём добавления нового поля в таблицы для последующей сортировки по нему.

Добавление сортируемого поля актуально в случае, если в таблице нет сортируемого уникального поля. Данное поле будет AUTO_INCREMENT, значение будет расти по мере добавления новых записей. Сортировка по нему будет соответствовать сортировке по дате создания.(Данный абзац добавлен после прочтения комментариев для внесения ясности)

Вообще говоря, этот приём не нов, например, https://medium.com/swlh/sql-pagination-you-are-probably-doing-it-wrong-d0f2719cc166. Но, на мой, используется незаслуженно редко.

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

Зачастую при запросах к БД используют offset и пока таблицы не более 100 000 строк запросы работают быстро, но как только объёмы данных увеличиваются и пользователю необходимо перейти на страницу 1001, возникают проблемы со скоростью ответа.

Замечу, что большинство поисковых систем не показывают количество найденных страниц, а просто предлагают перейти на следующую страницу.

Хотя ранее данная информация присутствовала, но видимо разработчики произвели оптимизацию:

Предложение состоит в том чтобы не использовать offset  в запросах и не выводить общее количество записей, а внедрить сортируемые данные в таблицы.

Произведём замеры при получение одних и тех же данных в сортируемой и не сортируемой таблице (выполняю локально):

Запрос к не сортируемой таблице:

sql: 'select table_name.* from table_name order by ID limit ? offset ?' (в соответствии с комментариями были внесены изменения: удален group by, добавлен order by)

Limit offset

Duration

Bytes

Deceleration

limit 10 offset 0

179ms

1320

limit 10 offset 1000

191ms

1320

1.06

limit 10 offset 100 000

606ms

1320

3.38

limit 10 offset 200 000

790ms

1320

4.41

limit 10 offset 300 000

864ms

1320

4.82

limit 10 offset 400 000

1115ms

1320

6.22

По результатам шести запросов с offset от 0 до 400 000, мы видим увеличение скорости выполнения запроса более чем в 6 раз. Это связанно с тем, что оператор offset является не эффективным и запрос выгрузил все 400 000 записей, а уже потом проверил их на соответствие условиям. Так же отдельного внимания стоит уделить запросу на получение значения общего числа найденных сущностей, так как данный запрос используется для указания числа страниц. Длительность выполнения данного запроса составляет около 3000ms на таблице с 400 000 записей. Данное время можно смело прибавлять к общему значению Duration, так как единожды данный запрос необходимо будет выполнить.

Пример запроса: 'select count(*) from table_name'.

Запрос к сортируемой таблице:

sql: 'select table_name.* from table_name where (table_name.sortID > ?) order by sortID asc limit ?' (в соответствии с комментариями было внесено изменение: удален group by)

Оператор offset  в данном запросе не используется.  В качестве переменной для сравнения с sortID используем sortID 0 или значение sortID последней сущности из предыдущего запроса.

SortID limit

Duration

Bytes

Deceleration

sortID 0 limit 10

183ms

1320

sortID 1000 limit 10

189ms

1320

1,032

sortID 100000 limit 10

190ms

1320

1,038

sortID 200000 limit 10

192ms

1320

1,049

sortID 300000 limit 10

179ms

1320

0,978

sortID 400000 limit 10

180ms

1320

0,983

По результатам шести запросов с sortID от 0 до 400 000, мы видим увеличение времени выполнения запроса близко к погрешности. Это связано с тем что БД загружает только 10 записей.

Результаты эксперимента на графике:

Ось X - Значение offset/sortID.

Ось Y - Время выполнения запроса в миллисекундах.

На графике наглядно видно, что в не сортируемых таблицах при значении offset/sortID > 1000 резко возрастает время выполнения запроса. Очевидно при увеличении количества записей время выполнения запроса будет расти.

Как оптимизировать существующие таблицы и запросы к БД.

  1. Добавляем в таблицу сортируемое поле. Создаём миграцию:

    ALTER TABLE table_name

    ADD COLUMN sortID bigint(15) UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE

    FIRST;

  2. Переписываем запросы:

    Было: 'select table_name.* from table_name order by ID limit ? offset ?' (limit =10, offset=100 000)
    Стало: 'select table_name.* from table_name where (table_name.sortID > ?) order by sortID asc limit ?' (sortID =100 000, limit =10).

    (в соответствии с комментариями были внесены изменения: удалены group by, добавлен order by)

Итог: Данные действия с таблицами позволят уменьшить время выполнения запросов в разы при объёмных таблицах и время выполнения запросов будет стабильным даже если таблицы будут большими. Но необходимо понимать, что за данную оптимизацию мы платим тем, что мы испытываем сложности с дополнительной сортировкой (например, по алфавиту), но на мой взгляд, эта плата не значительна. По сути простой и быстрый метод ускорения запросов, но во многих проектах до сих пор используется классический offset.

Tags:
Hubs:
Total votes 9: ↑0 and ↓9-9
Comments29

Articles