Как я могу ускорить запрос MySQL с большим смещением в предложении LIMIT? - PullRequest
25 голосов
/ 07 августа 2009

У меня проблемы с производительностью при LIMIT использовании mysql SELECT с большим смещением:

SELECT * FROM table LIMIT m, n;

Если смещение m скажем больше 1 000 000, операция будет очень медленной.

Я должен использовать limit m, n; Я не могу использовать что-то вроде id > 1,000,000 limit n.

Как я могу оптимизировать это утверждение для повышения производительности?

Ответы [ 6 ]

13 голосов
/ 07 августа 2009

Возможно, вы могли бы создать таблицу индексации, которая предоставляет последовательный ключ, относящийся к ключу в вашей целевой таблице. Затем вы можете присоединить эту таблицу индексации к вашей целевой таблице и использовать предложение 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;
9 голосов
/ 07 августа 2009

В Интернете есть сообщение в блоге о том, как лучше всего сделать выбор строк для показа , которые должны быть максимально компактными, таким образом: только идентификаторы; и получение полных результатов должно в свою очередь извлекать все нужные вам данные только для выбранных вами строк .

Таким образом, 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, или он ничего не улучшает, вопреки надежде, возможно, стоит разбить этот отдельный оператор на несколько операторов и записать идентификаторы в структуру данных.

Обновление : я нашел сообщение в блоге, о котором я говорил: это был Джеффа Этвуда "Все абстракции - неудачные абстракции" на Coding Horror.

5 голосов
/ 28 мая 2014

Если записи большие, возможно, из-за медленной загрузки данных. Если столбец id проиндексирован, то просто выбрать его будет гораздо быстрее. Затем можно выполнить второй запрос с предложением 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)
3 голосов
/ 31 мая 2011

Я не думаю, что нужно создавать отдельный индекс, если он есть в вашей таблице. Если это так, то вы можете упорядочить по этому первичному ключу, а затем использовать значения ключа для перехода:

SELECT * FROM myBigTable WHERE id > :OFFSET ORDER BY id ASC;

Другой оптимизацией было бы не использование SELECT *, а только ID, чтобы он мог просто прочитать индекс и не должен затем находить все данные (уменьшать накладные расходы ввода-вывода). Если вам нужны некоторые из других столбцов, то, возможно, вы могли бы добавить их в индекс, чтобы они читались с помощью первичного ключа (который, скорее всего, будет храниться в памяти и, следовательно, не требует поиска диска) - хотя это не подходит для всех случаев, так что вам придется играть.

Я написал статью с более подробной информацией:

http://www.4pmp.com/2010/02/scalable-mysql-avoid-offset-for-large-tables/

2 голосов
/ 16 декабря 2009

Ответ Пола Диксона - действительно решение проблемы, но вам нужно будет поддерживать таблицу последовательности и следить за тем, чтобы в ней не было пропусков строк.

Если это возможно, лучшим решением было бы просто убедиться, что в исходной таблице нет пробелов и начинается с идентификатора 1. Затем захватите строки, используя идентификатор для разбивки на страницы.

SELECT * ИЗ таблицы A, где id> = 1 и id <= 1000; <br /> SELECT * FROM table A, где id> = 1001 и id

и так далее ...

0 голосов
/ 05 марта 2016

Я недавно столкнулся с этой проблемой. Проблема состояла из двух частей, которые нужно было исправить. Сначала я должен был использовать внутреннее выделение в предложении 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, затем смещение стало невыносимо медленным. Изменение этого значения между BETWEEN в моем внутреннем запросе ускорило его для любой страницы. Я не уверен, почему MySql не ускорил OFFSET, но между ними, кажется, снова его накатывает.

...