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

 

posted @ 2019-08-05 16:25  TeyGao  阅读(727)  评论(0编辑  收藏  举报