暂无图片
暂无图片
2
暂无图片
暂无图片
1
暂无图片

MySQL生产实战优化(利用Index skip scan优化性能提升257倍)

原创 chengang 2025-03-17
292

背景

我们生产环境均采用单表多租户设计模式,即一个表中存有多个租户数据,在实际的业务中每个租户的数据是隔离的,即A租户不可能访问到B租户数据,假设租户ID的字段为profileid,我们每个索引都会将profileid放在最左第一列(主键除外),如(profileid,date,status)。

昨天运营需要统计一个线上数据,会跨租户查询,wherer条件中将不能带有租户ID字段,这将导致所有索引均失效,整个SQL查询非常非常慢。

问题SQL

实际语句更复杂,我将语句简单化了,说明优化过程

研发同学写的原版SQL

explain select profileid,billcode,billtype,billdate,billid from a1 where a1.billtype IN( 604) and a1.billdate >='2025-03-14' and a1.billdate <='2025-03-15'
复制

image.png
通过执行计划看到,将会是全表扫描,扫描300多万行

实际执行需要46327ms
image.png

性能优化

优化思路:
实际上此表有(profileid,billtype,billdate)联合索引,而billid 是主键字段,在整个语句中只有billcode不在上述联合索引之内,所以改造成先走Index skip scan,然后再自关联取billcode即可。

一个表中租户一般低于1000,所以完全符合Index skip scan的条件。我来改写,让这个语句走Index skip scan

优化的第一版SQL

explain select t.*,bi.billcode from ( select profileid,billtype,billdate,billid from a1 where a1.billtype IN( 604) and a1.billdate >='2025-03-14' and a1.billdate <='2025-03-15' ) t inner join erp_bill_index a1 as bi on t.billid = bi.billid
复制

image.png
通过执行计划发现,MySQL给我合并了子查询,虽然扫描行数未变,但走了索引覆盖扫描
虽然没有走到 index skip scan 但性能还是有较大提升 从46327ms提升到了546ms 提升84倍

image.png

优化的第二版SQL

explain select /*+no_merge() */ t.*,bi.billcode from ( select profileid,billtype,billdate,billid from a1 where a1.billtype IN( 604) and a1.billdate >='2025-03-14' and a1.billdate <='2025-03-15' ) t inner join a1 bi on t.billid = bi.billid
复制

强制用hint no_merge() 阻止MySQL的合并优化

image.png
在执行计划中看到扫描行数少了,也终于走到了 Index skip scan

性能继续提升 从46327ms提升到了178ms 提升260倍

image.png

Index skip scan的要求

1、索引为复合索引
2、查询为单表查询
3、查询不使用GROUP BY或DISTINCT
4、查询仅引用索引中的列
5、索引为(a,b,c)条件用到了b,c 且b、c设计不能为null

贴上官方的说明

Using this strategy decreases the number of accessed rows because MySQL skips the rows that do not qualify for each constructed range. This Skip Scan access method is applicable under the following conditions:
Table T has at least one compound index with key parts of the form ([A_1, …, A_k,] B_1, …, B_m, C [, D_1, …, D_n]). Key parts A and D may be empty, but B and C must be nonempty.
The query references only one table.
The query does not use GROUP BY or DISTINCT.
The query references only columns in the index.
The predicates on A_1, …, A_k must be equality predicates and they must be constants. This includes the IN() operator.
The query must be a conjunctive query; that is, an AND of OR conditions: (cond1(key_part1) OR cond2(key_part1)) AND (cond1(key_part2) OR …) AND …
There must be a range condition on C.
Conditions on D columns are permitted. Conditions on D must be in conjunction with the range condition on C.
Use of Skip Scan is indicated in EXPLAIN output as follows:
Using index for skip scan in the Extra column indicates that the loose index Skip Scan access method is used.
If the index can be used for Skip Scan, the index should be visible in the possible_keys column.

range-access-skip-scan

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

张静懿
暂无图片
3天前
评论
暂无图片 0
MySQL生产实战优化(利用Index skip scan优化性能提升257倍)
3天前
暂无图片 点赞
评论