执行计划FirstMatch

本文MySQL版本5.7

sql语句:

sql 复制代码
SELECT
	COUNT(*) 
FROM
	twthy01 t 
WHERE
	t.STATUS IN ( '115', '117' ) 
	AND (
		t.laboratory_code LIKE '01010204%' 
		OR t.laboratory_code LIKE '010102040108%' 
		OR t.laboratory_code LIKE '0101020302%'
	) 
	AND t.testing_no IN (
	SELECT
		t2.request_no 
	FROM
		twthy02 t2 
	WHERE
		t2.testing_id IN (
			8,
			9,
			10,
			11,
			12,
			13,
			14,
			15,
			16
		) 
	) 
	AND t.testing_no IN ( SELECT t4.request_no FROM tbzff04 t4 );

这个sql执行0.56秒,但是单独执行SELECT t4.request_no FROM tbzff04 t4

需要101秒,为什么整个sql又这么快。我们看看explain的结果:

MySQL 5.6 的优化器会识别出 ... WHERE col IN (SELECT ...) 这种模式,并将其转换为一种特殊的 JOIN,称为"半连接"。半连接的目标是:只关心外层表的行是否在内部表找到匹配,而不关心匹配了多少次

它有多种策略来实现,最有可能的是以下两种:

策略一:FirstMatch(首次匹配)

这是最高效的策略之一。它的执行逻辑如下:

  1. 开始扫描 twthy01 表(假设它有 1.9 万行)。
  2. 取出第一行 t
  3. 检查 t 是否满足 STATUSlaboratory_code 条件。假设满足。
  4. 关键一步 :现在要检查 t.testing_no IN (SELECT t4.request_no FROM tbzff04 t4)。优化器会去 tbzff04 表中查找 t.testing_no
  5. 因为 tbzff04 表的 request_no 上有索引(idx_request_no),这个查找极快(毫秒级)。
  6. 一旦找到第一个匹配项 ,优化器就立刻停止在 tbzff04 中的查找,并确认 t 这一行符合条件。它根本不会扫描 tbzff04 的其他行
  7. 返回步骤 2,处理 twthy01 的下一行。

对比一下:

  • 单独查询 :扫描 tbzff04500 万 行。
  • 子查询 :为 twthy01 的每一行,在 tbzff04 中进行一次快速的索引查找 ,并且找到就停 。总工作量大约是 19000 * (极快的索引查找时间),这和 101 秒比起来,简直是天壤之别。

策略二:Materialization(物化)

如果优化器认为 FirstMatch 不适用,它可能会选择物化策略:

  1. 先执行子查询 SELECT t4.request_no FROM tbzff04 t4
  2. 但它不会把结果返回给你,而是把结果(所有不重复的 request_no)存入一个临时的内存表 中,并为这个临时表建立一个哈希索引
  3. 然后,在扫描 twthy01 表时,直接用 t.testing_no 去这个哈希索引里查找,这也是极快的。

等等,这不还是需要 101 秒吗?

不一定。在物化过程中,优化器可能会利用一些并行处理或者更高效的读取方式。但更重要的是,即使这一步花了 101 秒,它也只执行一次 。而原始的 EXISTS 写法,可能会让 twthy02 的全表扫描执行 19000 次!相比之下,101 秒 的成本是可以接受的。但在你的案例中,FirstMatch 的可能性更大,因为它根本不需要物化整个表。

所以从这看出使用IN语句 并不是都是先把子查询查出来然后和主表比较

工作流程:

  1. 执行子查询 :数据库完整地执行 SELECT t4.request_no FROM qzxcl_lims_ys.tbzff04 t4
  2. 生成临时表 :将查询结果(几百万个 request_no)存入一个临时的、内存中的表(如果太大,可能会写在磁盘上)。
  3. 执行主查询 :扫描 twthy01 表,对于每一行,都去这个临时表里检查 t.testing_no 是否存在。

这确实是一种经典的执行方式。 如果子查询结果集很小,这种方式非常高效。

但是,如果子查询结果集非常大(比如您的 500 万行),这种方式就非常糟糕了!

  • 创建临时表需要消耗大量 CPU 和 I/O。
  • 临时表占用大量内存或磁盘空间。
  • 整个过程耗时极长(就像您单独执行需要 101 秒一样)。

2. 优化器的"智慧":选择最优策略

现代数据库优化器非常"聪明",它知道物化策略在结果集很大时是灾难。所以,它会评估多种执行策略的成本,然后选择成本最低的那一个。

对于 IN 子查询,优化器通常会将其转换为一种特殊的连接,称为半连接 。半连接的目标是"检查是否存在",而不是"获取所有匹配",所以它比普通 JOIN 更高效。

在半连接的大框架下,优化器有多种"武器"可以使用:

武器一:FirstMatch(首次匹配)------ 您的案例就是这个!

这是最高效的策略之一,专门为处理大结果集子查询而生。

工作流程:

  1. 根本不执行子查询! 优化器直接跳过了"物化"这一步。
  2. 扫描主表 :开始扫描 twthy01 表。
  3. 索引查找 :对于 twthy01 的每一行,直接利用 tbzff04 表上的 idx_request_no 索引,去查找 t.testing_no
  4. 找到就停 :一旦在索引中找到第一个匹配项,就立刻停止对 tbzff04 的查找,并确认 twthy01 的当前行符合条件。

对比一下:

  • 物化:我要先抄写整本 500 万行的电话簿,然后再去核对。
  • FirstMatch:我根本不抄电话簿,而是每来一个名字,我就直接去电话簿里按索引查找,找到就划掉,继续下一个。

显然,FirstMatch 在您的场景下是压倒性的最优解。

相关推荐
toooooop82 小时前
CentOS 7 系统上安装 **Nginx + MySQL 5.7 + PHP 7.3 + Redis** 的完整步骤
redis·mysql·nginx·centos·php7
i***51262 小时前
【数据库】MySQL的安装与卸载
数据库·mysql·adb
7***31882 小时前
若依微服务中配置 MySQL + DM 多数据源
android·mysql·微服务
M***29912 小时前
在 Ubuntu 上安装 MySQL 的详细指南
mysql·ubuntu·adb
J***51683 小时前
MySql中的事务、MySql事务详解、MySql隔离级别
数据库·mysql·adb
SamDeepThinking5 小时前
再次理解 MySQL B+ 树:以每页 10 行的聚簇索引为例
mysql
q***31835 小时前
MySQL---存储过程详解
数据库·mysql
q***42825 小时前
MySQL数据库误删恢复_mysql 数据 误删
数据库·mysql·adb