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

时间:2009-08-07 10:03:16

标签: mysql performance limit

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

SELECT * FROM table LIMIT m, n;

如果偏移量m大于1,000,000,则操作非常慢。

我必须使用limit m, n;我不能使用像id > 1,000,000 limit n这样的东西。

如何优化此声明以获得更好的性能?

6 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:9)

互联网上有一篇关于如何最好地使选择行的博客文章应该尽可能紧凑,因此:只有ids;并且生成完整的结果应依次获取您想要的所有数据仅用于您选择的行

因此,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语句,或者它没有改进任何东西,而不是希望,那么将这个单个语句分解为多个语句并将id捕获到数据结构中可能是值得的。

更新:我找到了我正在谈论的博客文章:这是Jeff Atwood关于Coding Horror的"All Abstractions Are Failed Abstractions"

答案 2 :(得分:5)

如果记录很大,那么加载数据的速度可能会很慢。如果id列被索引,那么只选择它会快得多。然后,您可以使用IN子句为相应的id执行第二次查询(或者可以使用第一个查询中的min和max id来表示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 :(得分:3)

如果您的表已有表单,我认为不需要创建单独的索引。如果是这样,那么您可以通过此主键进行排序,然后使用键的值来逐步执行:

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

另一个优化不是使用SELECT *而是使用ID,这样它就可以简单地读取索引,而不必定位所有数据(减少IO开销)。如果您需要其他一些列,那么也许您可以将这些列添加到索引中,以便使用主键(最有可能保存在内存中,因此不需要光盘查找)读取它们 - 尽管这不合适对于所有情况,所以你必须有一个游戏。

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

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

答案 4 :(得分:2)

Paul Dixon的回答确实是问题的解决方案,但您必须维护序列表并确保没有行间隙。

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

SELECT * FROM table A WHERE id> = 1 AND id< = 1000;
SELECT * FROM表A WHERE id> = 1001 AND id< = 2000;

依旧......

答案 5 :(得分:0)

我最近遇到过这个问题。问题是需要解决的两个部分。首先,我必须在我的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')

我第一次创建此查询时,我在MySql中使用了OFFSET和LIMIT。这工作正常,直到我超过第100页然后偏移开始变得无法忍受的缓慢。在我的内部查询中将其更改为BETWEEN会为任何页面加速。我不确定为什么MySql没有加速OFFSET,但介于两者之间似乎又卷回来了。