一、事情过程
今天突然有一个平时很正常的 sql 跑了2000多次,并且每次都跑2秒,差点把数据库搞挂了。
select o.* from
money o
inner join moneyPool p
where o.pool_id = p.id
and p.code = 'xushu'
and o.status = 'APPLIED'
and o.transOrderNo = 'xushu_good_luck'
二、分析
首先,我们要明白 join 的执行逻辑。不管是 left join、right join、inner join,join 的执行都分为驱动表、被驱动表。explain 的时候,第一行为驱动表,后面的为被驱动表。
以上表为例,money 有 id、pool_id、status、transOrderNo 字段,money 有联合索引 un_ps(pool_id, status);moneyPool 有 id、code 字段,moneyPool 有索引 code。moneyPool 使用自己的 id 与 money 的 pool_id 联系。所以整个查询过程如下:
查询过程
1.首先在驱动表 moneyPool 中根据 code = 'xushu' 进行查询,因为 code 有索引,所以很快。
即 sql:select * from moneyPool where code = 'xushu'
2.然后根据找出的 moneyPool 数据找出自己的 id,再使用 id 在 money 表中进行查询。因为 money有联合索引 un_ps(pool_id, status),所以会选中 un_ps(pool_id, status) 进行查询,即 sql:select * from money where pool_id = xx and status = 'APPLIED' and transOrderNo = 'xushu_good_luck'。因为 pool_id = xx 即 code = 'xushu' 的数据量比较大,所以虽然走了索引,但是还需要回表才能找到条件为 transOrderNo = 'xushu_good_luck' 的。最后需要扫描70w 行,导致结果比较慢。
3.筛选完后,将结果集合并,然后得出最后的结果
这个 sql 最大的问题是因为每次查询都需要回表 money 筛选,如果数据量小,其实问题不大,但偏偏满足 pool_id = xx 即 code = 'xushu' 的数据量有70w行,导致扫描了太多的行数,所以速度变慢。如果 un_ps 联合索引中,已经能筛选出 transOrderNo = 'xushu_good_luck' 的数据,那么最后只需要回表一行,速度大大增加。
所以,终极解决方案:加上 un_ps(pool_id, status, transOrderNo ) 联合索引。
3、总结
其实还有一个解决方案,在 money 表加区分度高的索引。比如 money 表有一个 transOrderNo 字段,一般编号都是唯一的,如果 transOrderNo 加索引的话,mysql 选取索引的时候就不会选取 un_ps(pool_id, status) 联合索引,而会选 transOrderNo 索引,因为这时候扫描的行数大大减小。
网友评论