zoukankan      html  css  js  c++  java
  • 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语句(如关联查询)来优化内外表位置。

  • 相关阅读:
    #Hadoop集群部署
    #Linux Keepalived 负载均衡
    #Linux LVS 负载均衡
    #Kafka 彻底删除topic
    #Linux Keepalived 双机热备
    Python #图片验证码
    Linux #tar
    MongoDB #$set的问题
    Django #CSRF
    keystone环境搭建(源码方式+yum方式)(ocata版本)
  • 原文地址:https://www.cnblogs.com/gaogao67/p/11137317.html
Copyright © 2011-2022 走看看