MySQL Execution Plan--IN子查询对UPDATE语句影响
问题描述
在系统中发现一条执行时间为为44652.060734秒(12.5小时)的慢SQL,SQL语句为:
UPDATE ob_internal_task SET OPERATE_STATUS = 0 WHERE business_no IN ( SELECT outbound_no FROM ob_internal_orderstatus WHERE wave_no = 'xxxxx' AND VSTATE = 10 AND outbound_no <> 'xxxxxxxx' ) AND business_type = 20;
对于执行计划为:
*************************** 1. row *************************** id: 1 select_type: UPDATE table: ob_internal_task partitions: NULL type: index possible_keys: NULL key: PRIMARY key_len: 8 ref: NULL rows: 1657847 filtered: 100.00 Extra: Using where *************************** 2. row *************************** id: 2 select_type: DEPENDENT SUBQUERY table: ob_internal_orderstatus partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 622710 filtered: 0.09 Extra: Using where
由于两个表上都使用全表扫描,需要循环外表ob_internal_task记录1654884次*内表ob_internal_orderstatus记录622596次=1030324158864(预估影响行数)
解决步骤
表ob_internal_task列business_no上有索引,没有被正确使用,而表上ob_internal_orderstatus缺少合适索引,因此首先优化IN子查询内部,创建索引:
ALTER TABLE ob_internal_orderstatus ADD INDEX `IDX_wave_no_VSTATE1` (`WAVE_NO`,`VSTATE`,`OUTBOUND_NO`);
创建完索引后执行计划变更为:
*************************** 1. row *************************** id: 1 select_type: UPDATE table: ob_internal_task partitions: NULL type: index possible_keys: NULL key: PRIMARY key_len: 8 ref: NULL rows: 1656801 filtered: 100.00 Extra: Using where *************************** 2. row *************************** id: 2 select_type: DEPENDENT SUBQUERY table: ob_internal_orderstatus partitions: NULL type: ref possible_keys: IDX_wave_no_VSTATE1 key: IDX_wave_no_VSTATE1 key_len: 131 ref: const,const,func rows: 1 filtered: 100.00 Extra: Using where; Using index
外表仍然使用全表扫描,检查两表关联列,发现都是VARCHAR类型,不存在隐式转换。
将UPDATE语句调整为SELECT测试,调整后的SQL为:
SELECT * FROM ob_internal_task WHERE business_no IN ( SELECT outbound_no FROM ob_internal_orderstatus WHERE wave_no = 'BC20190523155122' AND VSTATE = 10 AND outbound_no <> '14740794' ) AND business_type = 20;
对于执行计划为:
*************************** 1. ROW *************************** id: 1 select_type: SIMPLE TABLE: ob_internal_orderstatus PARTITIONS: NULL TYPE: ref possible_keys: IDX_wave_no_VSTATE1 KEY: IDX_wave_no_VSTATE1 key_len: 68 ref: const,const ROWS: 26 filtered: 90.00 Extra: USING WHERE; USING INDEX; LooseScan *************************** 2. ROW *************************** id: 1 select_type: SIMPLE TABLE: ob_internal_task PARTITIONS: NULL TYPE: ref possible_keys: idx_business_type,idx_business_no KEY: idx_business_no key_len: 303 ref: innerdelivery.ob_internal_orderstatus.OUTBOUND_NO ROWS: 3 filtered: 10.00 Extra: USING INDEX CONDITION; USING WHERE
对比UPDATE和SELECT语句的执行计划,发现内外表的位置发生变化,同时也导致表ob_internal_task上索引idx_business_no能被正常使用。
考虑到IN语句的影响,尝试将UPDATE语句中的依赖子查询调整为关联查询,调整后SQL为:
UPDATE ob_internal_task T1 INNER JOIN ( SELECT outbound_no FROM ob_internal_orderstatus WHERE wave_no = 'BC20190523155122' AND VSTATE = 10 AND outbound_no <> '14740794' ) AS T2 ON T1.business_no = T2.outbound_no SET T1.OPERATE_STATUS = 0 WHERE T1.business_type = 20;
调整后的执行计划为:
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: ob_internal_orderstatus partitions: NULL type: range possible_keys: IDX_wave_no_VSTATE1 key: IDX_wave_no_VSTATE1 key_len: 131 ref: NULL rows: 25 filtered: 100.00 Extra: Using where; Using index *************************** 2. row *************************** id: 1 select_type: UPDATE table: T1 partitions: NULL type: ref possible_keys: idx_business_type,idx_business_no key: idx_business_no key_len: 303 ref: func rows: 3 filtered: 10.00 Extra: Using where
使用关联更新的UPDATE语句能够正常使用索引,执行时间由原来44652.060734秒(12.5小时)缩短至的在30ms内。
PS1:相关表的统计信息未存在明显异常,排除统计信息导致执行计划异常。
优化总结
1、在优化DELETE/UPDATE语句时,通常会将DELETE/UPDATE语句改写成SELECT语句进行测试,以避免测试操作导致数据变更,需要注意两者的执行计划可能不同。
2、当IN语句生产的执行计划为DEPENDENT SUBQUERY类型时,需要考虑外表的循环数量,如果外表循环次数较大,可以考虑调整SQL语句(如关联查询)来优化内外表位置。