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

dluptydi  于 2023-01-29  发布在  Mysql
关注(0)|答案(6)|浏览(122)

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

SELECT * FROM table LIMIT m, n;

如果偏移量m例如大于1,000,000,则操作非常慢。
我确实必须使用limit m, n;我不能使用类似id > 1,000,000 limit n的东西。
如何优化此语句以获得更好的性能?

4sup72z8

4sup72z81#

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

ycl3bljg2#

如果记录很大,则加载数据可能会很慢。如果ID列已建立索引,则只需选择它就会快得多。然后,可以使用IN子句执行第二次查询,以获得相应的ID(或者可以使用第一次查询中的最小和最大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)
cnwbcb6i

cnwbcb6i3#

互联网上有一篇博客文章,介绍了如何最好地使要显示的行的 * 选择 * 尽可能紧凑,因此:只有身份证并且生成完整的结果应该反过来获取您想要的所有数据 仅用于您选择的行 *。
因此,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捕获到数据结构中。

更新:我找到了我谈论的博客文章:是杰夫·阿特伍德在《编程恐怖》里的"All Abstractions Are Failed Abstractions"

nlejzf6q

nlejzf6q4#

我认为如果你的表已经有了一个单独的索引,就没有必要再创建一个单独的索引了。如果是这样,你可以通过这个主键来排序,然后使用这个键的值来逐步执行:

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

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

xpcnnkqh

xpcnnkqh5#

Paul狄克逊的答案确实是这个问题的解决方案,但是您必须维护序列表并确保没有行间隙。
如果这是可行的,更好的解决方案是确保原始表没有行间隙,并从id 1开始,然后使用id获取行进行分页。
SELECT * FROM表格A,其中id〉= 1且id〈= 1000;
SELECT * FROM表格A,其中id〉= 1001且id〈= 2000;
等等...

q35jwt9p

q35jwt9p6#

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

$subQuery = DB::raw("( SELECT id FROM titles WHERE id BETWEEN {$startId} AND {$endId}  ORDER BY title ) as t");

然后,我可以使用它作为查询的from部分:

'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,但BETWEEN似乎又把它卷回来了。

相关问题