• MySQL Case--优化OR语句踩坑记录


    问题描述

    研发同事反馈某应用执行较慢,对应SQL为:

    UPDATE bs_serial_trac 
    SET SERIAL_IS_LOCK = 0,
    LOCK_VALUE = '',
    UPDATE_USER = 'transSys' 
    WHERE GOODS_NO = '4418095740626' 
    AND (
    PARENT_CODE = 'F9G7S19722001835' 
    OR SERIAL = 'F9G7S19722001835'
    );

    表bs_serial_trac上索引情况为:

    PRIMARY KEY (`ID`),
    UNIQUE KEY `idx_complex_serial_goodsNo` (`SERIAL`,`GOODS_NO`),
    KEY `idx_update_time` (`UPDATE_TIME`),
    KEY `idx_serial_goodsNo` (`GOODS_NO`),
    KEY `idx_parent_code` (`PARENT_CODE`),
    KEY `idx_lock_value` (`LOCK_VALUE`)

    由于使用OR条件,查询只能基于条件GOODS_NO = '4418095740626' 进行数据查找,其执行计划为:

    *************************** 1. row ***************************
               id: 1
      select_type: UPDATE
            table: bs_serial_trac
       partitions: NULL
             type: range
    possible_keys: idx_serial_goodsNo
              key: idx_serial_goodsNo
          key_len: 93
              ref: const
             rows: 404920
         filtered: 100.00
            Extra: Using where
    1 row in set (0.00 sec)

    由于GOODS_NO列选择性较差,满足条件的记录较多,导致查询性能较差:

    SELECT COUNT(1)
    FROM bs_serial_trac
    WHERE GOODS_NO = '4418095740626';
    
    +----------+
    | COUNT(1) |
    +----------+
    |   215447 |
    +----------+

    解决步骤

    问题很明显,由于OR语句导致索引无法正常使用,将OR调整为UNION ALL,调整后的SQL语句为:

    SELECT *
    FROM bs_serial_trac 
    WHERE GOODS_NO = '4418095740626' 
    AND PARENT_CODE = 'F9G7S19722007485' 
    UNION ALL
    SELECT *
    FROM bs_serial_trac 
    WHERE GOODS_NO = '4418095740626' 
    AND SERIAL = 'F9G7S19722007485'
    AND PARENT_CODE <> 'F9G7S19722007485'

    查询速度很快,由之前的800ms优化到10ms以下,看起来很完美,但是查询结果没数据。。。

    简单定位下,发现PARENT_CODE列类型为varchar(50) DEFAULT '',PARENT_CODE列值为NULL,做不等于判断时存在问题:

    SELECT 
    NOT(PARENT_CODE <=> 'F9G7S19722007485') AS C1,
    NOT(PARENT_CODE = 'F9G7S19722007485') AS C2,
    PARENT_CODE <> 'F9G7S19722007485' AS C3
    FROM bs_serial_trac 
    WHERE GOODS_NO = '4418095740626' 
    AND SERIAL = 'F9G7S19722007485' ;
    
    +----+------+------+
    | C1 | C2   | C3   |
    +----+------+------+
    |  1 | NULL | NULL |
    +----+------+------+

    因此改写为UNION ALL语句时需要改写为:

    SELECT *
    FROM bs_serial_trac 
    WHERE GOODS_NO = '4418095740626' 
    AND PARENT_CODE = 'F9G7S19722007485' 
    UNION ALL
    SELECT *
    FROM bs_serial_trac 
    WHERE GOODS_NO = '4418095740626' 
    AND SERIAL = 'F9G7S19722007485'
    AND NOT(PARENT_CODE <=> 'F9G7S19722007485')

    或改写为:

    SELECT *
    FROM bs_serial_trac 
    WHERE GOODS_NO = '4418095740626' 
    AND PARENT_CODE = 'F9G7S19722007485' 
    UNION ALL
    SELECT *
    FROM bs_serial_trac 
    WHERE GOODS_NO = '4418095740626' 
    AND SERIAL = 'F9G7S19722007485'
    AND (PARENT_CODE <> 'F9G7S19722007485' OR PARENT_CODE IS NULL)

    由于在UNION ALL的第二部分查询中,PARENT_CODE不用于索引查找,只用于数据过滤,因此两种方式都不会影响查询性能。

    确认使用UNION ALL性能满足需求后,将UPDATE操作改写为:

    UPDATE bs_serial_trac 
    SET SERIAL_IS_LOCK = 0,
    LOCK_VALUE = '',
    UPDATE_USER = 'transSys' 
    WHERE ID IN(
        SELECT ID FROM(
        SELECT ID
        FROM bs_serial_trac 
        WHERE GOODS_NO = '4418095740626' 
        AND PARENT_CODE = 'F9G7S19722007485' 
        UNION ALL
        SELECT ID
        FROM bs_serial_trac 
        WHERE GOODS_NO = '4418095740626' 
        AND SERIAL = 'F9G7S19722007485'
        AND (PARENT_CODE <> 'F9G7S19722007485' OR PARENT_CODE IS NULL)
        ) AS T1
    )

    其对于执行计划为:

    *************************** 1. row ***************************
               id: 1
      select_type: UPDATE
            table: bs_serial_trac
       partitions: NULL
             type: index
    possible_keys: NULL
              key: PRIMARY
          key_len: 8
              ref: NULL
             rows: 13270473
         filtered: 100.00
            Extra: Using where
    *************************** 2. row ***************************
               id: 2
      select_type: DEPENDENT SUBQUERY
            table: <derived3>
       partitions: NULL
             type: index_subquery
    possible_keys: <auto_key0>
              key: <auto_key0>
          key_len: 8
              ref: func
             rows: 2
         filtered: 100.00
            Extra: Using index
    *************************** 3. row ***************************
               id: 3
      select_type: DERIVED
            table: bs_serial_trac
       partitions: NULL
             type: ref
    possible_keys: idx_serial_goodsNo,idx_parent_code
              key: idx_parent_code
          key_len: 153
              ref: const
             rows: 1
         filtered: 5.00
            Extra: Using where
    *************************** 4. row ***************************
               id: 4
      select_type: UNION
            table: bs_serial_trac
       partitions: NULL
             type: const
    possible_keys: idx_complex_serial_goodsNo,idx_serial_goodsNo,idx_parent_code
              key: idx_complex_serial_goodsNo
          key_len: 695
              ref: const,const
             rows: 1
         filtered: 100.00
            Extra: NULL
    4 rows in set (0.00 sec)

    虽然按照主键ID去更新,但是由于使用IN语句,仍导致查询走全表扫描,性能极差,需要将IN查询转换成INNER JOIN:

    UPDATE
    bs_serial_trac AS T2
    INNER JOIN (
        SELECT ID
        FROM bs_serial_trac 
        WHERE GOODS_NO = '4418095740626' 
        AND PARENT_CODE = 'F9G7S19722007485' 
        UNION ALL
        SELECT ID
        FROM bs_serial_trac 
        WHERE GOODS_NO = '4418095740626' 
        AND SERIAL = 'F9G7S19722007485'
        AND (PARENT_CODE <> 'F9G7S19722007485' OR PARENT_CODE IS NULL)
    ) AS T1 
    ON T1.ID=T2.ID
    SET
    T2.SERIAL_IS_LOCK = 0,
    T2.LOCK_VALUE = '',
    T2.UPDATE_USER = 'transSys'

    修改后执行计划为:

    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: <derived2>
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 3
         filtered: 100.00
            Extra: NULL
    *************************** 2. row ***************************
               id: 1
      select_type: UPDATE
            table: T2
       partitions: NULL
             type: eq_ref
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 8
              ref: T1.ID
             rows: 1
         filtered: 100.00
            Extra: NULL
    *************************** 3. row ***************************
               id: 2
      select_type: DERIVED
            table: bs_serial_trac
       partitions: NULL
             type: ref
    possible_keys: idx_serial_goodsNo,idx_parent_code
              key: idx_parent_code
          key_len: 153
              ref: const
             rows: 1
         filtered: 5.00
            Extra: Using where
    *************************** 4. row ***************************
               id: 3
      select_type: UNION
            table: bs_serial_trac
       partitions: NULL
             type: const
    possible_keys: idx_complex_serial_goodsNo,idx_serial_goodsNo,idx_parent_code
              key: idx_complex_serial_goodsNo
          key_len: 695
              ref: const,const
             rows: 1
         filtered: 100.00
            Extra: NULL
    4 rows in set (0.00 sec)

    调整能正常按照主键去操作,性能有保障。

    <=>操作符

    <=> : NULL-safe equal. This operator performs an equality comparison like the = operator, but returns 1 rather than NULL if both operands are NULL, and 0 rather than NULL if one operand is NULL.
    
    参考: https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_equal-to
  • 相关阅读:
    struts2文件上传报错
    简述算法和程序的区别并举例说明
    JAVA中TreeMap集合筛选字母及每一个字符出现的次数
    Myeclipse2014破解步骤
    修改ubuntu的终端提示符
    gcc 引用math.h头文件,编译出现undefined reference to `pow‘等错误时,需要加参数lm.
    几篇文章
    gdb调试gcc出现:Missing separate debuginfos, use: debuginfoinstall glibcx.i686
    【达内C++学习培训学习笔记系列】C语言之三循环语句和数组
    code::block之spell checker配置
  • 原文地址:https://www.cnblogs.com/gaogao67/p/11303776.html
Copyright © 2020-2023  润新知