如何在LIMIT子句中使用大偏移量加速MySQL查询?

如何在LIMIT子句中使用大偏移量加速MySQL查询?,mysql,performance,limit,Mysql,Performance,Limit,当LIMITing带有大偏移量的mysqlSELECT时,我遇到了性能问题: SELECT * FROM table LIMIT m, n; 如果偏移量m大于1000000,则操作非常缓慢 我必须使用限制m,n;我不能使用像id>1000000 limit n这样的东西 如何优化此语句以获得更好的性能?也许您可以创建一个索引表,该表提供与目标表中的键相关的顺序键。然后,您可以将这个索引表连接到目标表,并使用where子句更有效地获取所需的行 #create table to store seq

LIMIT
ing带有大偏移量的mysql
SELECT
时,我遇到了性能问题:

SELECT * FROM table LIMIT m, n;
如果偏移量
m
大于1000000,则操作非常缓慢

我必须使用
限制m,n
;我不能使用像
id>1000000 limit n
这样的东西


如何优化此语句以获得更好的性能?

也许您可以创建一个索引表,该表提供与目标表中的键相关的顺序键。然后,您可以将这个索引表连接到目标表,并使用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;

在互联网上的某个地方有一篇博文,内容是关于如何最好地选择要显示的行,并使其尽可能紧凑,因此:只需ID;生成完整的结果应该反过来只获取所选行的所有数据

因此,SQL可能类似(未经测试,我不确定它是否真的会有任何好处):

如果您的SQL引擎过于原始,不允许使用这种SQL语句,或者它没有任何改进,那么将这条语句分解为多条语句并将ID捕获到数据结构中可能是值得的


更新:我找到了我所说的博文:这是杰夫·阿特伍德(Jeff Atwood)关于编码恐惧的文章。

保罗·迪克森(Paul Dixon)的答案确实是问题的解决方案,但你必须维护序列表并确保没有行间距

如果这是可行的,更好的解决方案是简单地确保原始表没有行间距,并且从id 1开始。然后使用id抓取行进行分页


从表A中选择*,其中id>=1,id=1001,id如果您的表已经有索引,我认为不需要创建单独的索引。如果是这样,则可以按此主键排序,然后使用该键的值单步执行:

SELECT * FROM myBigTable WHERE id > :OFFSET ORDER BY id ASC;
另一种优化方法是不使用SELECT*而只使用ID,这样它就可以简单地读取索引,而不必查找所有数据(减少IO开销)。如果您需要其他一些列,那么您可以将它们添加到索引中,以便使用主键读取它们(主键很可能保存在内存中,因此不需要光盘查找)-尽管这并不适用于所有情况,因此您必须进行播放

我写了一篇更详细的文章:


如果记录很大,则加载数据的速度可能会变慢。如果id列被索引,那么只选择它会快得多。然后,您可以使用IN子句对适当的ID进行第二次查询(或者可以使用第一次查询中的min和max ID构造WHERE子句)

慢:

快速:


我最近遇到了这个问题。问题有两部分需要解决。首先,我必须在我的FROM子句中使用一个内部select,它只对主键进行限制和偏移:

$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')

第一次创建这个查询时,我在MySql中使用了偏移量和限制。这很好,直到我通过了第100页,然后偏移开始变得令人无法忍受的慢。在我的内部查询中将其更改为介于两者之间可以加快任何页面的速度。我不知道为什么MySql没有加快偏移量,但中间的偏移量似乎又回来了。

我测试了您的SQL。但是它没有任何改进。如果您有一个基于表a的where子句,该怎么办?它将不起作用,因为它首先限制,然后应用where子句。如果在子查询的内部使用join,会降低性能,对吗?对我来说,这很有效,
selectid FROM…
query在一组近一百万行上的执行速度比
selectbunch,of,fields FROM…
快50倍。感谢Atwood的文章;那是一本有趣的书。但它并不建议你说什么就做什么;相反,它使用这种技术作为在这里工作的示例。我认为这篇文章的全部前提是数据库是复杂的野兽,没有一种解决方案可以适合所有情况(因此抽象不可避免地会“泄漏”)。这种方法只适用于不包含where条件的select语句。在我看来,这不是一个好的解决方案。如何保持此索引表的更新?在我的例子中,我必须按datetime列排序,并使用较大的偏移量,这会导致查询速度变慢。如果我创建这个支持表,我将需要在每次有新日期时重新插入,因为它不是按顺序出现的。我已经看到了这个解决方案,但是使用了临时表。如果我读得正确,那么您只是将id列从mytable复制到另一个表中(您必须更新这两个表)。你能不能在你只选择id的地方加入它自己?这就是我见过的大多数变通方法所做的。从表中选择*,其中id>1000 LIMIT 1000只是mysql,或者大多数dbs都以这种奇怪的方式工作?到目前为止,最好的解决方案是子查询(当您没有有序索引时)。首先查询并排序所有内容,然后放入偏移量。仅使用ID的想法可能是一个非常好的解决方案,我想这取决于存储引擎!这与许多其他解决方案非常相似,在这些解决方案中,您事先知道要从什么ID开始进行限制(可能有更优雅的方法来做到这一点)。其中的主要问题是,当您需要在中间显示页面时,您不知道要从何处开始(由WHERE子句决定)。我猜子查询中的按标题排序没有索引。您可以尝试使用explain来了解发生了什么,并创建新的索引。尝试索引文本可能会有问题。这实际上是这里最好的答案,符合Jeff Atwood在中链接的博客文章所描述的内容。另请参阅
SELECT id FROM table ORDER BY id DESC LIMIT 10 OFFSET 50000

SELECT * FROM table WHERE id IN (1,2,3...10)
$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')