现在我们主要讨论的内容主要是关于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 理解
评论区