Как я могу ускорить запрос MySQL с большим смещением в предложении LIMIT?
У меня возникают проблемы с производительностью, когда LIMIT
с mysql SELECT
с большим смещением:
SELECT * FROM table LIMIT m, n;
Если смещение m
, скажем, больше 1 000 000, операция выполняется очень медленно.
Мне нужно использовать limit m, n
; Я не могу использовать что-то вроде id > 1,000,000 limit n
.
Как я могу оптимизировать этот оператор для повышения производительности?
Ответы
Ответ 1
Возможно, вы могли бы создать таблицу индексирования, которая предоставляет последовательный ключ, относящийся к ключу в вашей целевой таблице. Затем вы можете присоединиться к этой таблице индексирования в свою целевую таблицу и использовать предложение where, чтобы более эффективно получать нужные строки.
#create table to store sequences
CREATE TABLE seq (
seq_no int not null auto_increment,
id int not null,
primary key(seq_no),
unique(id)
);
#create the sequence
TRUNCATE seq;
INSERT INTO seq (id) SELECT id FROM mytable ORDER BY id;
#now get 1000 rows from offset 1000000
SELECT mytable.*
FROM mytable
INNER JOIN seq USING(id)
WHERE seq.seq_no BETWEEN 1000000 AND 1000999;
Ответ 2
Там где-то в Интернете появляется сообщение в блоге о том, как лучше всего выбирать, какие строки показывать, должны быть как можно более компактными, а именно: только идентификаторы; и получение полных результатов должно, в свою очередь, извлекать все данные, которые вы хотите, только для выбранных строк.
Таким образом, SQL может быть чем-то вроде (непроверенный, я не уверен, что он действительно пригодится):
select A.* from table A
inner join (select id from table order by whatever limit m, n) B
on A.id = B.id
order by A.whatever
Если ваш SQL-движок слишком примитивен, чтобы разрешить подобные SQL-запросы или он ничего не улучшает, с надеждой, может быть целесообразно разбить этот единственный оператор на несколько операторов и зафиксировать идентификаторы в структуре данных.
Обновление: я нашел сообщение в блоге, о котором я говорил: это был Jeff Atwood "Все абстракции не соответствуют абстракциям" О ужасе кодирования.
Ответ 3
Если записи велики, медленность может возникать при загрузке данных. Если индексный столбец индексируется, то его выбор будет намного быстрее. Затем вы можете выполнить второй запрос с предложением IN для соответствующих идентификаторов (или может сформулировать предложение WHERE с использованием минимальных и максимальных идентификаторов из первого запроса.)
медленно:
SELECT * FROM table ORDER BY id DESC LIMIT 10 OFFSET 50000
быстро:
SELECT id FROM table ORDER BY id DESC LIMIT 10 OFFSET 50000
SELECT * FROM table WHERE id IN (1,2,3...10)
Ответ 4
Ответ Пол Диксон действительно является решением проблемы, но вам нужно будет поддерживать таблицу последовательностей и следить за тем, чтобы не было пробелов в строке.
Если это возможно, лучшим решением будет просто убедиться, что исходная таблица не имеет пробелов в строке и начинается с id 1. Затем возьмите строки, используя идентификатор для разбивки на страницы.
SELECT * FROM table A WHERE id >= 1 И id <= 1000;
SELECT * FROM table A WHERE id >= 1001 И id <= 2000;
и т.д.
Ответ 5
Я не думаю, что есть необходимость создать отдельный индекс, если в вашей таблице уже есть один. Если это так, вы можете заказать этот первичный ключ, а затем использовать значения ключа для перехода через:
SELECT * FROM myBigTable WHERE id > :OFFSET ORDER BY id ASC;
Другая оптимизация - это не использовать SELECT *, а просто идентификатор, чтобы он мог просто читать индекс и не должен затем находить все данные (уменьшить накладные расходы IO). Если вам нужны некоторые другие столбцы, возможно, вы можете добавить их в индекс, чтобы они считывались с помощью первичного ключа (который, скорее всего, будет храниться в памяти и, следовательно, не требует поиска на диске), хотя это не подходит для всех случаев, поэтому вам придется играть.
Я написал статью с подробностями:
http://www.4pmp.com/2010/02/scalable-mysql-avoid-offset-for-large-tables/
Ответ 6
Недавно я столкнулся с этой проблемой. Проблема состояла в том, чтобы исправить две части. Сначала мне пришлось использовать внутренний выбор в моем предложении FROM, который ограничивал меня и менял только для первичного ключа:
$subQuery = DB::raw("( SELECT id FROM titles WHERE id BETWEEN {$startId} AND {$endId} ORDER BY title ) as t");
Тогда я мог бы использовать это как часть из моего запроса:
'titles.id',
'title_eisbns_concat.eisbns_concat',
'titles.pub_symbol',
'titles.title',
'titles.subtitle',
'titles.contributor1',
'titles.publisher',
'titles.epub_date',
'titles.ebook_price',
'publisher_licenses.id as pub_license_id',
'license_types.shortname',
$coversQuery
)
->from($subQuery)
->leftJoin('titles', 't.id', '=', 'titles.id')
->leftJoin('organizations', 'organizations.symbol', '=', 'titles.pub_symbol')
->leftJoin('title_eisbns_concat', 'titles.id', '=', 'title_eisbns_concat.title_id')
->leftJoin('publisher_licenses', 'publisher_licenses.org_id', '=', 'organizations.id')
->leftJoin('license_types', 'license_types.id', '=', 'publisher_licenses.license_type_id')
В первый раз, когда я создал этот запрос, я использовал OFFSET и LIMIT в MySql. Это работало нормально, пока я не прошел мимо страницы 100, тогда смещение начало становиться невыносимо медленным. Изменение этого параметра МЕЖДУ в моем внутреннем запросе ускорило его для любой страницы. Я не уверен, почему MySql не ускорил OFFSET, но между ними, кажется, снова намотано.