问题描述

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

UPDATE bs_serial_trac
SET SERIAL_IS_LOCK = 0,
LOCK_VALUE = '',
UPDATE_USER = 'transSys'
WHERE GOODS_NO = ''
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 = ''; +----------+
| COUNT(1) |
+----------+
| 215447 |
+----------+

解决步骤

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

SELECT *
FROM bs_serial_trac
WHERE GOODS_NO = ''
AND PARENT_CODE = 'F9G7S19722007485'
UNION ALL
SELECT *
FROM bs_serial_trac
WHERE GOODS_NO = ''
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 = ''
AND SERIAL = 'F9G7S19722007485' ; +----+------+------+
| C1 | C2 | C3 |
+----+------+------+
| 1 | NULL | NULL |
+----+------+------+

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

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

或改写为:

SELECT *
FROM bs_serial_trac
WHERE GOODS_NO = ''
AND PARENT_CODE = 'F9G7S19722007485'
UNION ALL
SELECT *
FROM bs_serial_trac
WHERE GOODS_NO = ''
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 = ''
AND PARENT_CODE = 'F9G7S19722007485'
UNION ALL
SELECT ID
FROM bs_serial_trac
WHERE GOODS_NO = ''
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 = ''
AND PARENT_CODE = 'F9G7S19722007485'
UNION ALL
SELECT ID
FROM bs_serial_trac
WHERE GOODS_NO = ''
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

MySQL Case--优化OR语句踩坑记录的更多相关文章

  1. 复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

    从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负责的模块connector就派上了用场. ...

  2. CentOS7.4安装MySQL踩坑记录

    CentOS7.4安装MySQL踩坑记录 time: 2018.3.19 CentOS7.4安装MySQL时网上的文档虽然多但是不靠谱的也多, 可能因为版本与时间的问题, 所以记录下自己踩坑的过程, ...

  3. manjaro xfce 18.0 踩坑记录

    manjaro xfce 18.0 踩坑记录 1 简介1.1 Manjaro Linux1.2 开发桌面环境2 自动打开 NumLock3 系统快照3.1 安装timeshift3.2 使用times ...

  4. 你真的了解字典(Dictionary)吗? C# Memory Cache 踩坑记录 .net 泛型 结构化CSS设计思维 WinForm POST上传与后台接收 高效实用的.NET开源项目 .net 笔试面试总结(3) .net 笔试面试总结(2) 依赖注入 C# RSA 加密 C#与Java AES 加密解密

    你真的了解字典(Dictionary)吗?   从一道亲身经历的面试题说起 半年前,我参加我现在所在公司的面试,面试官给了一道题,说有一个Y形的链表,知道起始节点,找出交叉节点.为了便于描述,我把上面 ...

  5. SUCTF 2019 Upload labs 2 踩坑记录

    SUCTF 2019 Upload labs 2 踩坑记录 题目地址 : https://github.com/team-su/SUCTF-2019/tree/master/Web/Upload La ...

  6. 30多条mysql数据库优化方法,千万级数据库记录查询轻松解决(转载)

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  7. ABP框架踩坑记录

    ABP框架踩坑记录 ASP.NET Boilerplate是一个专用于现代Web应用程序的通用应用程序框架. 它使用了你已经熟悉的工具,并根据它们实现最佳实践. 文章目录 使用MySQL 配置User ...

  8. 转载:30多条mysql数据库优化方法,千万级数据库记录查询轻松解决

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  9. 浅谈MySQL中优化sql语句查询常用的30种方法 - 转载

    浅谈MySQL中优化sql语句查询常用的30种方法 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中使 ...

随机推荐

  1. Redis 启动 Please see the documentation included with the binary distributions for more details on the --maxheap flag.

    启动redis的时候,出现 主要就是说 没有足够的可用的空间,可以使用maxheap减少redis堆的大小.或者重启系统对系统分页文件进行碎片整理. 解决方法就是在启动的时候加个  --maxheap ...

  2. SDN实验---Ryu的应用开发(二)Learning Switch

    一:自学习交换机(二层MAC交换机)的编程思路 (一)明确问题 如何实现软件定义的自学习交换机? (二)设计解决方案 通过控制器来实现自学习交换算法,然后指导数据平面实现交换机操作 (三)确定具体的技 ...

  3. 研发团队是该制定OKR还是KPI?

    绩效管理和OKR的目标管理,是前行的两条腿,缺谁都会寸步难行.正确的做法是把企业的使命和任务,转化为经营目标,然后再用KPI.OKR等绩效管理工具,分解.执行.考核.. KPI是一套绩效管理的方法.全 ...

  4. 设置驱动的方法(Chrome 亲测ok)

    驱动下载地址 http://selenium-release.storage.googleapis.com/index.html package com.selenium.java.webdriver ...

  5. Bootstrap table插件 被选中的行颜色改变

    参考:https://www.jianshu.com/p/1bb4c37ef636 在 bootstrap-table.min.css 中修改源码 //选中行颜色 .fixed-table-conta ...

  6. Mac Pro 2015休眠掉电解决办法

    硬件:Mac Pro 2015 系统:MacOs Mojave 10.14.3 问题:合盖的时候,休眠1小时掉电10%,由于之前是128G原装盘不会有这个问题,后面购买了M.2转接卡,更换1T Int ...

  7. django:将query-set类型转为json类型

    import json data = json.dumps(list(my_table.objects.all().values())) return HttpResponse(data)

  8. (CSDN 迁移) JAVA多线程实现-支持定时与周期性任务的线程池(newScheduledThreadPool)

    前几篇文章中分别介绍了 单线程化线程池(newSingleThreadExecutor) 可控最大并发数线程池(newFixedThreadPool) 可回收缓存线程池(newCachedThread ...

  9. Jmeter在Http Rest接口中自动生成签名(Json格式请求参数)

    第一步: 签名的java类生成jar包,导入到jmeter的lib目录下(依赖的第三方包也要导入) 第二步:编写jmeter脚本,这里使用BeanShell 进行签名串的生成,目录结构如下: Bean ...

  10. 在有nginx做反向代理时候,如何获取用户真实Ip信息

    在获取用户的Ip地址时,不一定可以获取到用户真实的地址信息,这要看代理服务器的类型,代理服务器有普通匿名代理服务器,高匿代理服务器,像这种情况很难获取到用户真实的Ip地址 假如用户没有使用匿名代理服务 ...