慢查询业务场景:
原先在我们系统中要统计一些人员的单位 部门信息的数据情况,比如总的男女人数,每个单位下的男女人数等等,然后原来的sql是这样写的 根据一个单位的id 然后对一张表做出多个子查询进行查询,这时候统计记录 由于加载过的数据在mysql缓冲池里面,然后多个子查询也不是很慢,大概是几百毫秒就能统计出数据。
但是甲方在这个查询的基础上加了一个新需求,因为一个人员可能转部门,他需要把转过部门的人员恢复到原来部门进行一次重新的统计
这时候面对这种新需求,我们应该怎么做,刚开始我想的时候直接修改原来的sql,然后在前端加一个是否统计之前的还是之后的一个flag标志,进行查询,但是原来的sql写到了xml里面,一个sql大概有400行,他们统计调用了好几个接口,加起来修改的sql要有1800行,实在是工作量有点大,觉得这样不可行,然后我又想到 直接复制一份表出来,然后人员部门数据用原来的部门,但是这样业务也有侵入,因为如果这时候人员添加 ,人员转部门我都要在响应的接口上修改,这样修改的太多了,于是这两种方法都作废
最终我想到用一个视图,写一个查询,根据原来的人员的单位信息表和转单位表做一个连接,用
COALESCE()
函数 直接使用转单位前的数据进行统计,然后重新写一个xml文件,对照着之前的修改一下接口,这样能最少程度上达成任务,减少工作量,而且最少程度侵入业务
这时候我写完之后,自己本机测试没什么问题,大概2秒左右就能出数据,但是当上线到服务器上,测试发现该次查询居然足足用了17秒 如图
这怎么能行!
优化思路
于是我在想改变原来别人写的屎山sql费时又耗力,只能从自己写的视图入手,我先手动在服务器上查询了自己的创建视图的sql,发现用了2.3秒,然后sql又根据视图依次查询好几次,估计叠加起来才那么耗时,于是explan自己的sql语句 发现
这个type是最差的all,过滤的数据只是一百多条,可见性能非常差了,
于是理所当然想到加快查询 那么我们就进行加索引,但是我们怎么加如何加是一个问题
首先我们需要在满足最左前缀法则的基础上,然后不触到索引失效的场景,然后进行加索引
我的视图后面的sql是这样的
所以需要在两张表上加索引,因为我们的shzt 和scbz 都是常量,在sql执行的时候,mysql内部自动优化会先筛选过滤常量的数据,于是我们需要对zzy这张表的shzt 和scbz字段先加索引,然后再xsid和yxpcid 加的索引放到后面,组成一个联合索引, 对于xs表 scbz索引需要放到前面,xsid和yxpcid放到后面就可以了,这样会最大化的利用效率
加完索引我们再explain分析一下发现
type已经变成了ref 也是比较快了,而且都用到了索引。
其中为什么 在xs表中 只用到const 常量索引,是因为 在左连接中xs表的xsid和yxpcid只作为关联条件而不是作为过滤条件,在zzy中这两个字段是过滤条件根据zzy中的两个字段匹配xs中的字段,所以在zzy中能用到
因此索引字段的顺序必须与 “过滤优先级” 一致:过滤性强(能排除大部分数据)的字段放前面,关联字段放后面
那么有的就会稳了 为什么不把 常量索引放到后面,把字段索引放到前面呢?因为sql执行的时候会优先过滤常量,把常量索引放到前面使用索引的效率更高
无法优先通过过滤条件缩小范围
索引会先匹配最左侧的XSID
和YXPCID
,但这两个字段的值来自xs
表的关联(即zzy.XSID
要等于xs.XSID
,而xs.XSID
是动态的,取决于xs
表的查询结果)。
此时,数据库无法先通过SHZT=1
和C_SCBZ=0
过滤掉大部分无效记录,只能先根据xs
表的XSID
和YXPCID
去zzy
表的索引中找匹配的记录,再在这些记录中过滤SHZT=1
和C_SCBZ=0
。
这相当于 “先找关联,再过滤”,而过滤条件本可以更早排除大量数据,导致索引扫描范围变大。极端情况可能索引失效
如果zzy
表中XSID
和YXPCID
的重复值很多(比如大部分记录的XSID
都相同),数据库可能认为 “先通过索引找XSID
再过滤”,不如直接全表扫描后过滤更高效,此时索引会被放弃(type
变为ALL
)
假设 zzy
表有 10000 条记录:
- 其中
SHZT=1
且C_SCBZ=0
的记录只有 1000 条(有效数据)。 - 这 1000 条中,与
xs
表关联的XSID
、YXPCID
只有 100 条。
原索引 (SHZT, C_SCBZ, XSID, YXPCID)
:
- 先通过
SHZT=1
和C_SCBZ=0
从索引中定位到 1000 条有效记录。 - 再在这 1000 条中,通过
XSID
、YXPCID
关联xs
表,找到 100 条匹配记录。
总扫描:1000 条(高效)。
颠倒后索引 (XSID, YXPCID, SHZT, C_SCBZ)
:
- 先根据
xs
表的XSID
、YXPCID
去索引中找所有匹配的记录(假设有 5000 条,因为很多XSID
对应的记录SHZT
可能≠1)。 - 再在这 5000 条中过滤
SHZT=1
和C_SCBZ=0
,最终得到 100 条。
总扫描:5000 条(低效,比原索引多扫 4000 条)
因此我们需要这样设计索引才更高效
最后的优化查询时间为
后续测试该接口基本稳定在650到700ms