记一次MySQL查询优化
起因:
快下班的时候被同事A叫住,说是某个连表查询导致整个程序卡住,连Debug都停止了,让我帮忙瞅瞅,本着乐于助人的精神,我爽快的答应了。
排查:
大致代码如下:
1 | func GetCustomerInfo(){ |
因为同事A的描述是加了新的连表导致程序直接卡住,再加上打断点调试时都是走到最后一步卡住,倒也没有考虑可能是SQL的问题,甚至觉得是Gorm里面什么奇奇怪怪的错误导致的(原因大概是因为Gorm
的Debug()
没有触发,后续猜想应该是需要语句执行完毕才会打印出对应的SQL)。
因为上诉排查无果,也猜想到可能是因为慢SQL的原因,所以随即手写SQL测试:
1 | SELECT * FROM `customer` |
然后,很久过去了……。这是一个慢SQL确认无疑,不过另我好奇的是不过多join
了一个表而已,为何会这么夸张,让我祭出大杀器EXPLAIN
:
1 | EXPLAIN SELECT * FROM `customer` |
从上图不难看出对于表customer
与vest_customer_relation
为全表扫描,customer
倒是理所应当,但是对表vest_customer_relation
也全表扫描就属实有点离谱了,因为Mysql默认连接方式为笛卡尔积,所以上诉SQL运行时扫描的数据为33437 * 2 * 1 * 64686
,大概40亿 的样子,而且据我所知,同事A的业务要写完还需要连接一个表,无论为未写上去的表被扫描的数量是多少,后续的增长都是以40亿 为单位,这都是一个非常可怕的数量。
解决:
仔细观察EXPLAIN
的结果得知,表vest_customer_relation
是没有索引的,所以每次连接表的时候都会去全表扫描,这才导致了一次查询扫描了40亿 条数据,为表vest_customer_relation
加上索引即可:
1 | ALTER TABLE `gva`.`vest_customer_relation` |
此时执行上文SQL查看效果:
可以明显看到,新建的索引精准命中,表vest_customer_relation
只扫描了一行,总扫描条数也就降到了3W 左右,达到了理想的状态。
思考:
到了这里,问题已经得到了解决,但却不是最优的解决方案,由于用户数据增加的缘故,数据量还会继续增加,如果以后每次遇到类似的问题都通过索引来解决的话,显然不是最佳方案,索引滥用也会导致各种问题。业务问题业务解决,我们应该避免更多笛卡尔积的产生,将SQL拆分,通过业务代码将数据组装才是最佳的解决方案,简单拆分上诉SQL得到:
1 | EXPLAIN SELECT * FROM `customer` |
因为提前删除了索引,所以两段SQL分别扫描了3W 和6W 而已,加起来也不过10W ,后续在加上索引,完全在可接受的范围之类,后续通过索引之类的优化可以达到更佳。取消所有连表,最后扫描的行数应该为40行,这才是最佳解决方案。
后记:
从SQL语句不难看出,此需求为一个分页查询,那么上诉解决方案仍不是最佳解决方案,卖个关子,可以小小的思考下。