26

MySQL分页offset过大性能问题与优化 - DaemonCoder

 4 years ago
source link: https://www.daemoncoder.com/a/MySQL%E5%88%86%E9%A1%B5offset%E8%BF%87%E5%A4%A7%E6%80%A7%E8%83%BD%E9%97%AE%E9%A2%98%E4%B8%8E%E4%BC%98%E5%8C%96/4d513d3d?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MySQL分页offset过大性能问题与优化 - DaemonCoder

MySQL分页offset过大性能问题与优化
DaemonCoder | 2018-11-17 18:48:10

最近被DBA报了一个慢查询,一条sql要执行几秒甚至十几秒,直接导致cpu idle报警,幸好线上没有出事故。这里记录下问题的详细信息和解决方案。

Mysql表结构大概可以简化为这样:

CREATE TABLE `t` (

`a` int(11) NOT NULL,

`b` int(11) DEFAULT NULL,

`c` int(11) DEFAULT NULL,

PRIMARY KEY (`a`),

KEY `idx_c` (`c`)

) ENGINE=InnoDB

a、b、c三列,a列是主键,c列上有索引(当然线上真实的表结构比这个更复杂,此处只是为了说明问题,略去不相关字段)。

这个表的数据量已经达到千万级别。

导致慢查询的sql为:

SELECT b FROM t WHERE c<1000 LIMIT 2000000, 500

可以看到这个一个分页查询,从位置2000000处开始,取500条数据,问题的原因正是这个过大的分页起点导致。mysql分页查询会并不是直接跳过前2000000再取出500条数据,而是把前2000000条和后面的500条都取出来,再把前2000000条抛弃,这样的话,上面的慢查询相当于从表中取2000500条数据,这么大的数据量必然会慢。

sql修改为:

SELECT b FROM (SELECT a FROM t WHERE c<1000 LIMIT 2000000, 500) ta INNER JOIN t tb ON ta.a = tb.a

这种方式先用一个子查询表的主键(还是和原来一样带有过大分页),结果做为一个临时表,再和原来的t表JOIN,查出需要的字段。

这种方式不仔细看的话,也是要查出2000500条数据,因为子任务的where和limit设置和原来一样,关键就在于子任务SELECT出来的是a字段(t表的主键),而不是像原来直接b字段,这样查出500条数据后再和原有的表join再查出需要的数据字段b,下面详细分析下这个细节带来的性能差异。

基础知识:innodb的索引分为聚集索引和辅助索引,innodb是用聚集索引组织数据的,辅助索引上只存了一个主键,按辅助索引查询数据时,先从辅助索引对应记录的主键,再用主键去聚集索引查具体的数据字段。(这里不详细分析两个种索引的区别,不了解可以自行百度)

上面的慢sql会从辅助索上查2000500条数据,对于每一条数据还要从聚集索引上查一次。修改后的sql会从辅助索引上查出2000500条主键,由于辅助索引上本身就有主键,所以这2000500无需再去聚集索引查,生成临时表后再把这500条数据去聚集索引查出b字段,sql从聚集索引中查2000500条数据变成了只需要查500条,并且b字段在真实的情况往往是大量数据的字段,因此修改前后的sql性能差别很大(这里我理解修改前的sql按辅助索引顺序查询时,再去查聚集索引就不再是顺序读了,而是随机的离散读,也是一部分性能差的原因,具体只是自己的猜测,没有验证)。

修改前后的sql耗时从几秒降到了几十毫秒(这个差异和实际情况有关)。


微信公共号:

wx.jpeg?v=201912062350
toutiao.jpeg?v=201912062350

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK