当SQL语句中包含冗余或可以提前计算的子查询时,会增加查询分析和执行的开销,影响数据库性能。此问题在ORM框架(Object-Relational Mapping,即对象关系映射)自动生成复杂嵌套查询的场景中尤为突出。PolarDB MySQL版的子查询优化功能,通过在优化器阶段重写SQL,能够自动移除不必要的子查询、提前计算常量子查询,从而简化执行计划,显著提升查询性能。
适用范围
产品系列:集群版、标准版。
内核版本:MySQL 8.0.2,且修订版本需为8.0.2.2.19及以上版本。
开启子查询优化
通过设置loose_simplify_subq_mode参数来控制此优化功能的行为。
PolarDB集群参数在控制台与会话中修改方式存在差异,详细区别如下:
在PolarDB控制台上修改:
兼容性说明:部分集群参数在PolarDB控制台上均已添加MySQL配置文件的兼容性前缀loose_。
操作方法:找到并修改这些带
loose_前缀的参数。
在数据库会话中修改(使用命令行或客户端):
操作方法:当您连接到数据库,使用
SET命令修改参数时,请去掉loose_前缀,直接使用参数的原始名称进行修改。
参数名称 | 级别 | 描述 |
| Global/Session | 控制该功能的主开关。取值范围如下:
|
优化场景与示例
场景一:消除冗余的SELECT嵌套
场当子查询仅仅是为了包裹一个聚合函数或表达式,而没有其他复杂逻辑时(例如SELECT (SELECT SUM(a) FROM t2) FROM dual),优化器会剥离掉外层的SELECT,直接执行核心的表达式。
-- 出现在投影列
SELECT (SELECT SUM(a) FROM t2) FROM dual;
-- 优化后
SELECT SUM(`test`.`t2`.`a`) AS `sum(a)` FROM `test`.`t2`
-- 出现在HAVING子句中
SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b))));
-- 优化后
SELECT SUM(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` HAVING (0 <> count(`testdb`.`t2`.`b`))准备测试数据:
DROP TABLE IF EXISTS t2; CREATE TABLE t2 ( id INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT ); INSERT INTO t2 (a, b) VALUES (10, 100), (20, NULL), (50, 200), (120, NULL);关闭优化进行查询:首先,在会话中关闭子查询优化功能,以观察优化前的执行计划。
SET simplify_subq_mode = 'OFF'; EXPLAIN SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b))));SHOW warnings; -- 返回结果 /* select#1 */ select sum(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` having (0 <> (/* select#2 */ select (/* select#3 */ select (/* select#4 */ select count(`testdb`.`t2`.`b`)))))开启优化进行查询:现在,在会话中子查询优化功能,并执行相同的查询。
SET simplify_subq_mode = 'ON'; EXPLAIN SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b))));SHOW warnings; -- 返回结果 /* select#1 */ select sum(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` having (0 <> count(`testdb`.`t2`.`b`))
场景二:提前判断[NOT] EXISTS子查询
当[NOT] EXISTS子查询的条件可以被静态推断为始终为真(结果非空)或始终为假(结果为空)时,优化器会直接将[NOT] EXISTS子句替换为TRUE或FALSE,从而避免执行子查询。
-- 非空集合
SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2);
-- 优化后
SELECT * FROM t1
-- 空集合 WHERE/HAVING子句=false, 或者LIMIT 0
SELECT * FROM t1 WHERE EXISTS(SELECT max(a) FROM t2 HAVING 1=2 );
-- 优化后
SELECT * FROM t1 WHERE false准备测试数据:
DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1 (id INT); CREATE TABLE t2 (val INT); INSERT INTO t1 VALUES (1), (2);关闭优化进行查询:首先,在会话中关闭子查询优化功能,以观察优化前的执行计划。
SET simplify_subq_mode = 'OFF'; EXPLAIN SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2);SHOW warnings; -- 返回结果 /* select#1 */ select `testdb`.`t1`.`id` AS `id` from `testdb`.`t1` where exists(/* select#2 */ select max(`testdb`.`t1`.`id`) from `testdb`.`t2`)开启优化进行查询:现在,在会话中子查询优化功能,并执行相同的查询。
SET simplify_subq_mode = 'ON'; EXPLAIN SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2);SHOW warnings; -- 返回结果 /* select#1 */ select `testdb`.`t1`.`id` AS `id` from `testdb`.`t1`
场景三:为ANY/ALL子查询的常量投影添加LIMIT 1
当ANY或ALL子句中的子查询只查询常量(不涉及任何列)时,获取多行相同的值是没有意义的。优化器会自动为这类子查询添加LIMIT 1,避免对子查询的表进行不必要的全表扫描。
-- 优化前
SELECT * FROM t1 WHERE a > ANY (SELECT 1 FROM t2);
-- 优化后
SELECT * FROM t1 WHERE a > ANY (SELECT 1 FROM t2 LIMIT 1);生产环境使用建议
充分测试:该优化在绝大多数情况下保证逻辑等价。但在少数依赖于特定执行顺序或子查询执行次数的场景下,可能存在风险。建议在核心业务上线前,在预发或测试环境开启此功能进行充分回归测试。
定期更新统计信息:部分优化(如推断空集/非空集)依赖于表的统计信息。陈旧的统计信息可能导致优化器做出次优选择。建议定期对表执行
ANALYZE TABLE。