本文基于MySQL 5.7

问题背景

最近,发现业务线程卡死在读取数据库(MySQL 5.7),数据库CPU飙升到了100%,经过定位发现是下面这样一个SQL(由用户触发的查询,可以理解为查看这个群组的所有用户买过哪些商品)导致的:

select * from merchandise where id in (select merchandise_id from indent where customer_id in (select id from customer where name in("zhx","ypy"))
);

虽然听说MySQL5.6开始已经对于子查询做了很多查询优化,但是看来还是不够啊

MySQL历史追溯

MySQL针对子查询的优化一直不够好,在5.5版本及以前,根据《高性能MySQL》这本书说的,MySQL针对子查询的优化是优化为 exists 的方式来执行,变成逐条记录进行遍历:

5.7版本,优化并不是这样简单的优化了:而是转换为临时表加半连接优化。

举例分析与半连接优化的局限

假设我们有三张表:

用户表:

drop table if exists customer;
create table if not exists customer(id varchar(64) primary key,name varchar(64) unique key,address text
);

商品表:

drop table if exists merchandise;
create table if not exists merchandise(id varchar(64) primary key,name varchar(64) unique key,description text
);

订单表:

drop table if exists indent;
create table if not exists indent (id varchar(64) primary key,customer_id varchar(64),merchandise_id varchar(64),key (customer_id, merchandise_id)
);

用户和商品是一种多对多的关系。
假设他们的数据如下:

mysql> select * from customer;
+----+------+--------------------------------+
| id | name | address                        |
+----+------+--------------------------------+
| 1  | zhx  | Xueyuannanlu, Haidian, Beijing |
| 2  | ypy  | Zhichunlu, Haidian, Beijing    |
| 3  | tt   | Dog House, Haidian, Beijing    |
+----+------+--------------------------------+
3 rows in setmysql> select * from merchandise
;
+----+------------+-------------+
| id | name       | description |
+----+------------+-------------+
| 1  | Computer   | Lenovo      |
| 2  | Perfume    | Dior        |
| 3  | Chocolate  | Dove        |
| 4  | YummyBones | Pedigree    |
| 5  | IPhone     | Apple       |
+----+------------+-------------+
5 rows in setmysql> select * from indent
;
+----+-------------+----------------+
| id | customer_id | merchandise_id |
+----+-------------+----------------+
| 1  | 1           | 1              |
| 5  | 1           | 2              |
| 2  | 2           | 2              |
| 3  | 2           | 3              |
| 4  | 3           | 4              |
+----+-------------+----------------+
5 rows in set

假设我们想获取用户"zhx"购买过的所有商品,通过子查询应该是这样实现:

select * from merchandise where id in (select merchandise_id from indent where customer_id in (select id from customer where name in ("zhx"))
);

那这个的查询计划是什么样呢?
通过如下两个命令获取:

explain select distinct merchandise.id, merchandise.name, merchandise.description from merchandise
join indent on merchandise.id = indent.merchandise_id
join customer on indent.customer_id = customer.id
where customer.name in ("zhx");show warnings;

结果是:

show warnings;拆解出来的执行SQL伪代码是:

/* select#1 */ select `test`.`merchandise`.`id` AS `id`,`test`.`merchandise`.`name` AS `name`,`test`.`merchandise`.`description` AS `description` from
`test`.`customer` semi join (`test`.`indent`) join `test`.`merchandise`
where (
(`test`.`merchandise`.`id` = `test`.`indent`.`merchandise_id`)
and (`test`.`indent`.`customer_id` = '1')
and ('zhx' = 'zhx')
)

可以看到MySQL5.7针对这个子查询确实做了足够的优化,这里我们也看到了,MySQL通过半连接(semi join)优化了这次子查询。同时,由于存在性检查,先拿到了’zhx’的id为1,并且原来的customer.name in ("zhx")其实相当于customer.name = "zhx",所以条件可以改写为test.indent.customer_id = ‘1’。

我们再来看看出问题的子查询场景,针对某组用户查询购买过得商品:

select * from merchandise where id in (select merchandise_id from indent where customer_id in (select id from customer where name in ("zhx","ypy"))
);

这个语句的查询计划是:

/* select#1 */ select `test`.`merchandise`.`id` AS `id`,`test`.`merchandise`.`name` AS `name`,`test`.`merchandise`.`description` AS `description` from
`test`.`merchandise` semi join (`test`.`customer` join `test`.`indent`)
where (
(`<subquery2>`.`merchandise_id` = `test`.`merchandise`.`id`)
and (`test`.`indent`.`customer_id` = `test`.`customer`.`id`)
and (`test`.`customer`.`name` in ('zhx','ypy'))
)

这个居然是个针对merchandise表的全扫描!怪不得线上数据库扛不住。
但是为什么呢?我们参考下MySQL5.7官方文档的查询优化章节:
https://dev.mysql.com/doc/refman/5.5/en/statement-optimization.html

针对大部分IN的子查询语句,会被优化成半连接和中间表的执行机制。

什么是半连接,A semi join B可以理解为A inner join B但是结果只包含B的数据;

中间表比较好理解,就是中间结果保存成一个临时表。

还有A inner join B inner join CA inner join (B inner join C)的结果应该是一样的。

那么我们可以联想到一个优化,就是如果where条件里面包含C=?这样的条件,那么用A inner join (B inner join C)替换A inner join B inner join C可以提升很大效率。

获取用户"zhx"购买过的所有商品就是用这个定律,从

`merchandise` semi join (`test`.`customer` join `test`.`indent`)

变成了

`test`.`customer` semi join (`test`.`indent`) join `test`.`merchandise`

customer semi join indent表之后还是保留indent表和merchandise表进行join,相当于没有中间表

获取用户组多个用户"zhx","ypy"购买过的所有商品
由于没有优化,所以保持原有的:

`merchandise` semi join (`test`.`customer` join `test`.`indent`)

MySQL的semi join和中间表的优化缺陷在这里就体现出来了:
由于是merchandise去semi join中间结果,但是限制条件里面没有明确的merchandise限制,而且是和中间表进行join,所以针对merchandise进行全扫描。
这个其实很奇怪,为啥优化来优化去,变成了一个效果更差的扫描(这应该是三方面引起,一是semi join,二是没有明确的semi join要保留的表的有索引字段的条件限制,三是semi join的是中间表)

结论

MySQL针对多重子查询的优化还有待提升,最好还是用distinct和join的效率更好。

通过MySQL5.7子查询的坑联想到的MySQL查询优化规律与注意点相关推荐

  1. mysql子查询为什么不走索引_解决MySQL中IN子查询会导致无法使用索引问题

    测试表如下: CREATE TABLE`test_table` ( `id`int(11) NOT NULLAUTO_INCREMENT, `pay_id`int(11) DEFAULT NULL, ...

  2. MySQL子查询的优缺点_浅谈mysql的子查询

    浅谈mysql的子查询 mysql的子查询的优化一直不是很友好,一直有受业界批评比较多,也是我在sql优化中遇到过最多的问题之一,你可以点击这里 ,这里来获得一些信息,mysql在处理子查询的时候,会 ...

  3. mysql 子查询概念_聊聊MySQL的子查询

    1. 背景 在之前介绍MySQL执行计划的博文中已经谈及了一些关于子查询相关的执行计划与优化.本文将重点介绍MySQL中与子查询相关的内容,设计子查询优化策略,包含半连接子查询的优化与非半连接子查询的 ...

  4. 【转】ORACLE中的子查询 ---OCP--047--46

    "子查询"就是查询中嵌套着另一个查询,也即通过SELECT语句的嵌套使用形成子查询.当我们不知道特定的查询条件时,可以用子查询来为父查询提供查询条件以获得查询结果. ORACLE中 ...

  5. MySQL子查询原理分析

    01 前言 子查询,通俗解释就是查询语句中嵌套着另一个查询语句.相信日常工作中接触到 MySQL 的同学都了解或使用过子查询,但是具体它是怎样实现的呢? 查询效率如何? 这些恐怕好多人就不太清楚了,下 ...

  6. mysql怎么子查询_在mysql中如何进行子查询?

    在mysql中,子查询是指将一个查询语句嵌套在另一个查询语句中,可以在SELECT.UPDATE和 DELETE语句中配合WHERE子句进行实现,WHERE子句中语法格式为"WHERE (另 ...

  7. sharding子查询_ShardingSphere 分页

    分页性能 性能瓶颈 查询偏移量过大的分页会导致数据库获取数据性能低下,以MySQL为例: SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10 这句S ...

  8. in not 效率太慢_mysql in 子查询效率慢的优化方法详解

    表结构如下,文章只有690篇. 文章表article(id,title,content) 标签表tag(tid,tag_name) 标签文章中间表article_tag(id,tag_id,artic ...

  9. MySQL开发技巧 第二禅(子查询中匹配两个值、解决同属性多值过滤的问题、计算累进税的问题)

    一.如何在子查询中匹配两个值 mysql子查询的使用场景及其好处 1.什么是子查询?             当一个查询是另一个查询的条件时,称之为子查询(可以在curd中) 2.常见的子查询使用场景 ...

最新文章

  1. 用 Python 制作可视化报表,这也太快了!
  2. JavaScript 判断对象中是否有某属性
  3. AndroidStudio gradle配置
  4. html三元运算符 模板,AngularJS模板中的三元运算符
  5. 安装android sdk,后出现导出错误,提示命令行找不到解决方案
  6. 挖掘建模-分类与预测-决策树
  7. greenDao 3.0基础
  8. 【洛谷】NOIP2018原创模拟赛DAY1解题报告
  9. 统计局:去年12月天然气、电力生产增长较快
  10. Flutter 进阶篇-所有知识点架构
  11. java web 编程技术 答案_《javaweb编程技术》课后习题答案.docx
  12. 质量值体系 Phred33 和 Phred 64 的由来 及其在质量控制中的实际影响
  13. javascript 判断为负数_JavaScript判断数字正负数
  14. elementui表格鼠标滑轮控制横向滚动
  15. 警惕新骗术:虚假二维码生成器盗取 4.6 万美元!
  16. Kubernetes 认证证书过期处理
  17. docx4j word to html,使用Docx4J生成 html
  18. UNITY与Mac⭐三、Unity打包苹果应用的教程
  19. 【问题解决方案】电脑使用微信等第三方工具给鼠标右键和下拉列表截图
  20. 上海市高等学校计算机一级成绩查询,上海计算机一级考试成绩查询入口

热门文章

  1. c语言 浮点数的加减乘除,浮点数的加减乘除运算步骤
  2. 小米9 android q 内测,小米MIUI 10新版安卓q系统内测开启 支持机型小米9
  3. SpringBoot IntelliJ IDEA
  4. 白天求生存,晚上谋发展
  5. 2022危险化学品经营单位主要负责人考试模拟100题及答案
  6. c语言求幂 编程,C语言求幂计算的高效解法
  7. SIM900A 无法连接SIM卡,SIM_VCC引脚一直保持低电平的问题解决方法
  8. 【生活小感】:关于poppin的思考
  9. paddlepaddle下载安装
  10. 安装 Microsoft.VisualStudio.Devenv.Msi 报错