MySQL LIMIT X, Y замедляется при увеличении X - PullRequest
1 голос
/ 24 февраля 2020

У меня есть база данных с около 600 000 списков, при просмотре их на странице с нумерацией страниц я использую этот запрос для ограничения записей:

SELECT file_id, file_category FROM files ORDER BY file_edit_date DESC LIMIT 290580, 30

На первых страницах LIMIT 0, 30 загружается за несколько мс То же самое для LIMIT 30,30, LIMIT 60,30, LIMIT 90,30 и др. c. Но когда я перехожу к концу страниц, выполнение запроса занимает около 1 секунды.

Индексы, вероятно, не связаны, это также происходит, если я запускаю это:

SELECT * FROM `files` LIMIT 400000,30

Не уверен почему. Есть ли способ улучшить это?

Если нет лучшего решения, было бы плохой практикой просто загружать все записи и l oop поверх них на странице PHP, чтобы увидеть, если запись находится внутри диапазона нумерации страниц и распечатываете его?

Сервер i7 с 16 ГБ оперативной памяти; MySQL Сервер совместной работы 5.7.28; таблица файлов составляет около 200 МБ

вот my.cnf, если это имеет значение

query_cache_type = 1

query_cache_size = 1G

sort_buffer_size = 1G

thread_cache_size = 256

table_open_cache = 2500

query_cache_limit = 256M

innodb_buffer_pool_size = 2G

innodb_log_buffer_size = 8M

tmp_table_size=2G

max_heap_table_size=2G

Ответы [ 3 ]

0 голосов
/ 24 февраля 2020

LIMIT был изобретен для уменьшения размера набора результатов, он может использоваться оптимизатором, если вы заказываете набор результатов с использованием индекса.

При использовании LIMIT x,n серверу требуется обработать x + n строк, чтобы доставить результат. Чем выше значение для x , тем больше строк должно быть обработано.

Здесь приведен вывод объяснения из простой таблицы, имеющей уникальный индекс для столбца a :

MariaDB [test]> explain select a,b from t1 order by a limit 0, 2;
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------+
| id   | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------+
|    1 | SIMPLE      | t1    | index | NULL          | PRIMARY | 4       | NULL | 2    |       |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------+
1 row in set (0.00 sec)

MariaDB [test]> explain select a,b from t1 order by a limit 400000, 2;
+------+-------------+-------+-------+---------------+---------+---------+------+--------+-------+
| id   | select_type | table | type  | possible_keys | key     | key_len | ref  | rows   | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+--------+-------+
|    1 | SIMPLE      | t1    | index | NULL          | PRIMARY | 4       | NULL | 400002 |       |
+------+-------------+-------+-------+---------------+---------+---------+------+--------+-------+
1 row in set (0.00 sec)

При выполнении приведенных выше операторов (без EXPLAIN) время выполнения для LIMIT 0 составляет 0,01 секунды, для LIMIT 400000 0,6 секунды.

Поскольку MariaDB не поддерживает LIMIT в В подзапросе вы можете разделить ваши операторы SQL на два оператора:

Первый оператор извлекает идентификаторы (и должен только читать индексный файл), второй оператор использует идентификатор, полученный из первого оператора:

MariaDB [test]> select a  from t1 order by a limit 400000, 2;
+--------+
| a      |
+--------+
| 595312 |
| 595313 |
+--------+
2 rows in set (0.08 sec)

MariaDB [test]> select a,b from t1 where a in (595312,595313);
+--------+------+
| a      | b    |
+--------+------+
| 595312 | foo  |
| 595313 | foo  |
+--------+------+
2 rows in set (0.00 sec)
0 голосов
/ 24 февраля 2020

Внимание: я собираюсь использовать какой-то сильный язык. Компьютеры большие и быстрые, и они могут обрабатывать больше, чем могли бы даже десятилетие за go. Но, как вы выяснили, есть пределы. Я собираюсь указать на множество ограничений, которым вы угрожали; Я попытаюсь объяснить, почему ограничения могут быть проблемой.

Настройки

query_cache_size = 1G

ужасно. Всякий раз, когда в таблицу записывается, Q C сканирует 1 ГБ в поисках любых ссылок на эту таблицу, чтобы очистить записи в Q C. Уменьшите это до 50М. Это само по себе ускорит всю систему.

sort_buffer_size = 1G
tmp_table_size=2G
max_heap_table_size=2G

- это плохо по другой причине. Если у вас есть несколько соединений, выполняющих сложные запросы, для каждого из них может быть выделено много оперативной памяти, что приводит к потере оперативной памяти, что может привести к обмену и, возможно, сбоям. Не устанавливайте их выше, чем около 1% ОЗУ.

В общем, не изменяйте значения вслепую в my.cnf. Наиболее важным параметром является innodb_buffer_pool_size, который должен быть больше, чем ваш набор данных, но не должен превышать 70% от доступно RAM.

загрузить все записи

Ой! Стоимость переноса всех этих данных с MySQL на PHP нетривиальна. Как только он достигнет PHP, он будет храниться в структурах, которые не предназначены для огромных объемов данных - 400030 (или 600000) строк могут занимать 1 ГБ внутри PHP; это, вероятно, уничтожило бы его «memory_limit», что привело к краху PHP. (Хорошо, просто умираю с сообщением об ошибке.) Можно увеличить этот предел, но тогда PHP может привести к нехватке памяти sh MySQL, что приведет к обмену или, возможно, исчерпанию пространства подкачки. Какой беспорядок!

OFFSET

Что касается большого OFFSET, почему? У вас есть пользователь, пролистывающий данные? И он почти на странице 10000? Его покрывают паутины?

OFFSET должен прочитать и перешагнуть 290580 строк в вашем примере. Это дорого.

Для способа разбивки на страницы без этих издержек см. http://mysql.rjweb.org/doc.php/pagination.

Если у вас есть программа, "просматривающая" все строки 600 КБ, 30 в то время совет для "помните, где вы остановились" в этой ссылке будет очень хорошо работать для такого использования. Он не «замедляется».

Если вы делаете что-то другое; что это?

Нумерация страниц и пробелов

Не проблема. Смотрите также: http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks, который больше ориентирован на прохождение через весь стол. Основное внимание уделяется эффективному способу продвижения 30-го ряда вперед. (Это не обязательно лучше, чем запоминание последних id.)

Эта ссылка нацелена на DELETEing, but can easily be revised to SELECT`.

Немного математики для сканирования таблицы из 600 строк, 30 строк за один раз:

Мои ссылки: затронуты строки 600К. Или вдвое больше, если вы смотрите вперед с LIMIT 30,1, как предлагается во второй ссылке.

OFFSET ..., 30 должно касаться (600K / 30) * 600K / 2 строк - около 6 миллиардов row.

(Следствие: изменение 30 на 100 ускорит ваш запрос, хотя все равно будет мучительно медленным. Это не ускорит мой подход, но это уже довольно быстро.)

0 голосов
/ 24 февраля 2020

Может оказаться, что добавление следующего индекса поможет повысить производительность:

CREATE INDEX idx ON files (file_edit_date DESC, file_id, file_category);

Если используется, MySQL потребуется только одно сканирование индекса для получения количества записей с некоторым смещением. Обратите внимание, что мы включили столбцы в предложение select, чтобы индекс мог охватывать весь запрос.

...