我在使用大偏移量LIMIT
的 mysql时遇到性能问题:SELECT
SELECT * FROM table LIMIT m, n;
如果偏移m
量大于 1,000,000,则操作非常缓慢。
我必须使用limit m, n
;我不能使用类似的东西id > 1,000,000 limit n
。
如何优化此语句以获得更好的性能?
我在使用大偏移量LIMIT
的 mysql时遇到性能问题:SELECT
SELECT * FROM table LIMIT m, n;
如果偏移m
量大于 1,000,000,则操作非常缓慢。
我必须使用limit m, n
;我不能使用类似的东西id > 1,000,000 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 可能类似于(未经测试,我不确定它是否真的有什么好处):
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 上的“所有抽象都是失败的抽象” 。
如果记录很大,则缓慢可能来自加载数据。如果 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)
如果您的表已经有一个索引,我认为没有必要创建一个单独的索引。如果是这样,那么您可以按此主键排序,然后使用键的值来逐步执行:
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/
Paul Dixon 的回答确实是解决问题的方法,但是您必须维护序列表并确保没有行间隙。
如果可行,更好的解决方案是简单地确保原始表没有行间隙,并从 id 1 开始。然后使用 id 抓取行进行分页。
SELECT * FROM table A WHERE id >= 1 AND id <= 1000;
SELECT * FROM table A WHERE id >= 1001 AND id <= 2000;
等等...
我最近遇到了这个问题。问题是要解决两个部分。首先,我必须在我的 FROM 子句中使用一个内部选择,它只在主键上为我做限制和偏移:
$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 的速度,但似乎又把它卷了回来。