MySQL性能优化(八)exists子查询优化一例
一个例子
SELECT u.id userId, u.mobile, u.created_date createdDate FROM `user` u LEFT JOIN user_cash_detail ucd ON u.id= ucd.user_id WHERE 1= 1 AND EXISTS( SELECT 1 FROM borrow b WHERE b.user_id= u.id AND b.borrow_no LIKE '202001011212XXX%') ORDER BY u.id limit 13
这个查询需要执行120秒,
执行计划:
从执行计划中来看都有这良好的索引,而且需要扫描的rows 也很少,此条SQL性能应该比较好,为什么会执行需要120s 的时间呢?
此时需要了解下exists查询的执行过程:先执行主查询,然后用主查询的结果,检查exists子句中的子查询是否能匹配。
如果主表返回的纪录数比较大,exists 运行的次数就会大,导致整个查询的运行时间长。
在上面这个例子中,exists子查询中有一个b.borrow_no的条件,看起来过滤性比较高,可以尝试对SQL进行改写:
SELECT distinct u.id userId, u.mobile, u.created_date createdDate FROM borrow b join `user` u on b.user_id= u.id where b.borrow_no LIKE '202001011212XXX%' order by u.id limit 13
执行计划:
将exists子查询改写为常规的表关联,有几个地方需要注意:
1、原始SQL中并没有使用到user_cash_detail表,可以将left join去掉。
2、Exists 改成join后,由于borrow表user_id不唯一,SQL改写后可能会有重复数据出现,需要添加distinct,
SQL改写后,执行时间从120秒提升到3毫秒。
总结
SQL优化的一个基本思想是尽早过滤尽可能多的数据。这本例中,先执行exists中的子查询可以提前过滤掉大量数据。当然有时候需要对SQL进行一些改写。