记一次生产慢 sql 索引优化及思考 | 京东云技术团队
一 问题重现
夜黑风高的某一晚,突然收到一条运营后台数据库慢 sql 的报警,耗时竟然达到了 60s。
看了一下,还好不是很频繁,内心会更加从容排查问题,应该是特定条件下没有走到索引导致,如果频繁出现慢查询,可能会将数据库连接池打满,导致数据库不可用,从而导致应用不可用。
二 问题排查
报警自带定位慢 sql 语句,这个是很早就上线的一条 sql 语句,下面对 sql 语句进行了简化:
select * from xxx where gear_id=xxx and examine=xxx order by id desc limit 10,这是个简单的根据流量池 gear_id 查询,按照主键 id 倒序进行分页查询 10 条数据的语句。
在 examine=2 时查询速度很快,但是在 examine=3 时,查询速度极慢,然后分别在不同的 examine 下查看执行计划,得到的执行计划都是一致的。
查看执行计划,发现 possible_keys 中有 idx_gear_id 索引,但是实际用到的 key 却是 PRIMARY,并且 extra 中明确用了 where 条件进行数据过滤。到现在就明白了这个 sql 是在主键聚簇索引上进行扫描,然后用 where 语句条件进行过滤,时间耗费在这了。
这个也解释了为什么 examine 在不同状态下的耗时不一样,取决于 where 过滤扫描的行数,扫描行数越多,执行越慢,但同一个问题是都没走到我们已有的索引 idx_gear_id。
当单表数量较小时,无论有没有索引,或者走主键索引扫描或者普通索引都很快,很容易忽略这些问题,此时的表现就是你好,我好,大家好,然后随着数据量的增大,当达到千万级别或者亿级时,慢查询的问题就凸显出来了。
三 原理剖析
为什么 mysql 会选择这个不合适的主键聚簇索引?
以常用的 InnoDb 存储引擎为例,看一下聚簇索引和非聚簇索引查询区别:
聚簇索引:通常就是按照每张表的主键构造一颗 B+树,叶子节点中存放的就是整张表的行记录数据,即数据和主键都在索引上
非聚簇索引:表的二级索引字段(比如唯一索引,联合索引等)构造的一颗 B+树,叶子节点存储的是 Key 字段+主键值,即非聚集索引的叶节仍然是索引节点,但它有一个指向最终数据索引的指针。
聚簇索引查询原理:
非聚簇索引查询原理(二级索引查询):
由以上的索引数据结构可以看出,因为聚簇索引将索引和数据保存在同一个 B+树中,因此通常从聚簇索引中获取数据比非聚簇索引更快,而非聚簇索引在获取到叶子节点的主键后,需要再次查询主键索引,即回表查询行记录数据。当然如果查询的列只是索引字段,比如查询姓名和年龄,可以创建联合索引,即索引存储的内容即为需要查询的内容,这种查询速度往往比主键索引更快,这种索引查询又称为覆盖索引。
什么是回表?
将以上的索引数据映射成常见的用户表 user 的索引为例,上面的聚簇索引就是以 id 字段为主键的索引,name 字段为非聚簇索引,还有 age 等其他表字段是非索引字段,示例 sql:select * from user where id = 1; 这条 SQL 语句就不需要回表。原因是根据主键的查询方式,则只需要搜索 id 聚簇索引这棵 B+ 树,就可以查到对应的数据。
但当我们使用非聚簇索引 name 这个索引来查询 name = b 的记录时就要用到回表。原因是通过 name 这个二级索引查询方式,则需要先搜索 name 索引树,然后得到主键 id,即 PK 的值为 1,再到主键 id 聚簇索引树再搜索一次。这种根据二级索引查询到主键 id,再根据主键 id 查询主键聚簇索引的过程就称为回表。
回到为什么 mysql 会选择这个不合适的主键聚簇索引问题本身,mysql 执行器认为使用二级索引查出来的数据太多了,还需要基于磁盘做临时存储进行排序,然后排序取出 10 条,然后进行回表查询字段,性能可能会很差,所以采用了直接采用了按顺序扫描主键聚簇索引,和 where 条件 gear_id=xxx and examine=xxx 进行对比,最多放 10 条即可,这种情况就是数量小的时候没问题,但是当数据量大的时候,就需要一直扫描所有的数据,直到查到符合 where 条件的 10 条数据为止,同时耗时也急剧增长。
四 解决问题
为了快速解决问题,可以采用强制索引 force index,即在写 sql 语句时指定使用具体的索引
sql 示例 :select * from xxx force index (idx_gear_id) where gear_id=xxx and examine=3 order by id desc limit 10,强制使用 idx_gear_id 这个索引。
以下为使用强制索引的执行计划:
可以看到实际使用的索引 key 就是 idx_gear_id,执行耗时在几百毫秒,运营后台的业务人员完全可接受。
五 长期优化
由于表的数据越来越多,查询条件错综复杂,还有用 json 字段查询问题,决定将数据异构到 es 查询,将 json 字段打平,es 天然支持复杂的查询条件,查询响应更快。
es 数据同步方案:
在 ES 数据同步链路中,通过京东科技中间件 DTS 监听数据库的 binlog,将索引字段(查询条件字段)及业务唯一 id 写入 ES。
在业务运营查询时,根据复杂的查询条件,先去 ES 查询,将业务唯一 id 查出,再根据业务唯一 id 去 DB 中查询业务明细数据,同时解决了业务查询的复杂性和查询性能。
作者:京东科技 张石磊
来源:京东云开发者社区 转载请注明来源
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/9e7e13a06b3457817d244b9d3】。文章转载请联系作者。
评论