侧边栏壁纸
博主头像
ProSayJ 博主等级

Talk is cheap. Show me the code.

  • 累计撰写 43 篇文章
  • 累计创建 16 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

40-MySQL是如何基于各种规则去优化执行计划

YangJian
2025-06-28 / 0 评论 / 0 点赞 / 0 阅读 / 0 字

现在我们主要讨论的内容主要是关于MySQL在执行查询时如何通过查询重写来优化SQL语句,以达到更高的执行效率。MySQL会在查询执行前对SQL语句进行一定的优化和改写,帮助系统选择更优的执行计划。

1. 查询重写

1.1. 常见的SQL改写规则

MySQL会自动根据一系列规则对查询语句进行优化。虽然这些改写规则看起来可能非常琐碎,但它们的目的都是为了简化SQL语句、提高执行效率,确保执行计划的选择更加高效。

1.1.1. 删除无关紧要的括号

如果SQL语句中存在冗余的括号,MySQL会自动去除它们。例如:

SELECT * FROM t1 WHERE (name = 'Tom');

MySQL会将这个查询改写成:

SELECT * FROM t1 WHERE name = 'Tom';

这种改写有助于简化查询,避免不必要的计算。

1.1.2. 常量替换

如果SQL语句中存在可以替换的常量,MySQL会自动进行常量替换。例如:

SELECT * FROM t1 WHERE i = 5 AND j > i;

MySQL会将其改写为:

SELECT * FROM t1 WHERE i = 5 AND j > 5;

这种优化减少了计算的复杂性,让查询更直接。

1.1.3. 处理无意义的条件

如果SQL语句中包含冗余或无意义的条件,MySQL会直接删除它们。例如:

SELECT * FROM t1 WHERE b = b AND a = a;

这种语句显然没有实际作用,MySQL会将其改写为:

SELECT * FROM t1;

这种优化能消除无效的计算,减少查询成本。

说明:

b = b:如果 b 是一个 字段,那么 b = b 并 不是恒等式,因为它的值可能为 NULL。

  • 如果某一行 b 为 NULL,那么 b = b 为 false(因为 NULL != NULL)。

  • 所以该条件 不会始终为 true,MySQL 不能认为它恒为真而省略。

a = a:同理,a 为字段,也会因 NULL 而判断为 false。

1=1 :

  • 是绝对恒等表达式。会被优化器移除,因为它不影响逻辑结果。

1.2. 复杂查询的优化:SQL重写示例

有时候,MySQL会对较为复杂的查询进行更深层次的优化。例如,下面的查询:

SELECT * FROM t1 JOIN t2 ON t1.x1 = t2.x1 AND t1.id = 1;

这个查询显然是要从t1表中查找id=1的记录,并且与t2表进行连接。MySQL可以将其改写为:

SELECT t1.x1, t2.* FROM t1 JOIN t2 ON t1.x1 = t2.x1 WHERE t1.id = 1;

更进一步,MySQL会先查询t1表中id=1的记录,然后将t1表中该记录的字段常量值与t2表进行连接:

SELECT t1.x1 AS 'x1_value', t2.* FROM t1 JOIN t2 ON 'x1_value' = t2.x1 WHERE t1.id = 1;

这种改写有效地减少了不必要的扫描操作,使得查询的执行计划更加高效。

1.3. 结语

MySQL的查询重写是基于一系列规则自动进行的,它有助于简化SQL语句,优化执行计划。通过这些优化,MySQL能够在查询执行前消除一些冗余的计算和无意义的操作,从而提高查询效率。虽然这些规则可能看起来琐碎,但它们对于查询的优化有着显著的影响。

2. 通过执行计划优化子查询的性能

下面的内容主要是关于子查询的执行原理,以及如何通过执行计划优化子查询的性能。具体来说,我们分析了几种典型的子查询以及IN语句结合子查询的优化方式。

2.1. 子查询执行的基本原理

子查询是指在一个查询语句中嵌套另一个查询语句,MySQL在执行时会将其分解为两个步骤:

2.1.1. 简单子查询

比如这个典型的子查询:

SELECT * FROM t1 WHERE x1 = (SELECT x1 FROM t2 WHERE id = xxx);

执行时,MySQL首先会执行子查询部分:

SELECT x1 FROM t2 WHERE id = xxx;

然后将子查询的结果值应用到外层查询:

SELECT * FROM t1 WHERE x1 = 子查询的结果值;

这个外层查询基本上就是一个单表查询,使用单表查询的执行方式。

2.1.2. 依赖父查询的子查询

另一个例子是:

SELECT * FROM t1 WHERE x1 = (SELECT x1 FROM t2 WHERE t1.x2 = t2.x2);

在这种情况下,子查询中的WHERE条件依赖于外层查询(t1.x2 = t2.x2),意味着MySQL必须对t1表的每一行执行子查询。每次外层查询中的一条数据都会触发子查询执行,这样的查询效率非常低,因为需要重复执行子查询。、

2.2. 子查询优化:IN语句结合子查询

2.2.1. 普通的IN语句

假设我们有如下的查询:

SELECT * FROM t1 WHERE x1 IN (SELECT x2 FROM t2 WHERE x3 = xxx);

在这种情况下,我们可能会认为MySQL会先执行子查询,然后对t1表进行全表扫描,判断每一行数据是否出现在子查询的结果集中。但是,这种方法效率非常低,特别是当t1表的数据量较大时。

2.2.2. 👍👍优化后的IN语句执行方式(结果集物化,新增索引,二次加速)

MySQL会将这个子查询进行优化。首先,执行子查询:

SELECT x2 FROM t2 WHERE x3 = xxx;

然后,MySQL会将子查询的结果集物化,即将查询结果保存到一个临时表中(也叫物化表)。这个临时表可能使用MEMORY存储引擎存放,如果结果集较大,则可能存放到磁盘,并使用B+树聚簇索引来存储。物化表通常会建立索引,从而使得后续的查询更加高效。

2.2.3. 反向思考:优化的执行计划

假设t1表的数据量非常大(例如10万条记录),而子查询的结果集很小(例如500条记录),此时MySQL会反过来进行优化:

  • 将物化表作为主要的扫描对象,首先扫描物化表。

  • 对于每一条物化表的记录,MySQL会通过t1表的索引(如x1字段的索引)进行快速查找,判断t1表中的x1值是否在物化表中。

这种优化方式通过减少不必要的全表扫描,显著提高了查询效率。

2.3. 总结

  • 子查询的执行过程通常是先执行内部查询(子查询),然后使用结果集执行外部查询。

  • 当子查询中使用IN语句时,MySQL通过物化子查询结果并为其创建索引,优化了后续的查询过程。

  • 如果子查询结果较小,MySQL可以反过来优化执行计划,优先扫描物化表,减少对大表的扫描,提高查询效率。

通过了解这些优化规则,大家可以更好地理解MySQL在执行复杂查询时如何自动调整执行计划,避免低效的全表扫描,提高整体查询性能。

3. Semi Join

下面我们讨论了MySQL中对子查询执行计划优化的一种方式——Semi Join(半连接)。

3.1. Semi Join的概念

Semi Join(半连接)是一种优化策略,用于改写一些特定的子查询。它的目的是提高查询的效率,特别是在IN语句结合子查询时。

例如,考虑以下SQL:

SELECT * FROM t1 WHERE x1 IN (SELECT x2 FROM t2 WHERE x3 = xxx);

MySQL可能会将其内部转换为类似于以下的查询:

SELECT t1.* FROM t1 SEMI JOIN t2 ON t1.x1 = t2.x2 AND t2.x3 = xxx;

注意,这里SEMI JOIN不是MySQL提供的显式语法,而是MySQL内部用于优化子查询的一种方法。

SEMI JOIN的语义是:只要 t1 表中的某条记录在t2表中有符合条件的记录(即t1.x1 = t2.x2和t2.x3 = xxx),就将t1中的这条记录保留。

3.2. Semi Join的作用

  • Semi Join的核心思想是避免对子查询结果集的全表扫描,从而提高查询效率。

  • 它通过直接在连接条件中判断t1和t2表是否有符合条件的记录,来决定是否包含t1中的数据。

这种优化方式尤其适用于 IN语句 结合子查询的情况,可以避免将子查询的结果集加载到内存中,而是通过半连接的方式,直接在连接条件中筛选出符合条件的数据。

//TODO 详细的 Semi Join 理解

0

评论区