多表JOIN操作是复杂分析查询中的常见性能瓶颈。PolarDB MySQL版的连接(JOIN)消除功能,可在查询优化阶段识别并移除不必要的JOIN操作,以简化执行计划、减少I/O和计算量,提升查询性能。
适用范围
产品系列:集群版、标准版。
内核版本:MySQL 8.0.2,且修订版本需为8.0.2.2.31.1及以上版本。
开启连接(JOIN)消除
通过设置join_elimination_mode参数来控制此优化功能的行为。
PolarDB集群参数在控制台与会话中修改方式存在差异,详细区别如下:
在PolarDB控制台上修改:
兼容性说明:部分集群参数在PolarDB控制台上均已添加MySQL配置文件的兼容性前缀loose_。
操作方法:找到并修改这些带
loose_前缀的参数。
在数据库会话中修改(使用命令行或客户端):
操作方法:当您连接到数据库,使用
SET命令修改参数时,请去掉loose_前缀,直接使用参数的原始名称进行修改。
参数名称 | 级别 | 描述 |
| Global/Session | 控制该功能的主开关。取值范围如下:
|
优化场景示例
当前功能支持在以下六种场景中自动进行连接消除。
场景一:消除ON条件为常假的左连接(LEFT JOIN + FALSE COND)
LEFT JOIN的ON条件恒为FALSE时,该连接无意义。优化器会移除内表,并将其所有列替换为NULL。
-- 优化前
SELECT ..., ti1.*, ti2.*, ... FROM ... LEFT JOIN (ti1, ti2, ...) ON FALSE;
-- 优化后
SELECT ..., NULL, NULL, ... FROM ...;消除条件
LEFT JOIN的ON条件恒为FALSE的表达式(例如1=0或FALSE)。
场景示例
准备环境。
DROP TABLE IF EXISTS orders; CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT, order_date DATE ); DROP TABLE IF EXISTS customers; CREATE TABLE customers ( customer_id INT PRIMARY KEY, customer_name VARCHAR(100) ); INSERT INTO orders VALUES (1, 101, '2023-10-01');优化前:关闭JOIN消除,执行一个
ON FALSE的查询。SET SESSION join_elimination_mode = 'OFF'; EXPLAIN SELECT o.*, c.* FROM orders o LEFT JOIN customers c ON FALSE;SHOW warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `testdb`.`o`.`order_id` AS `order_id`,`testdb`.`o`.`customer_id` AS `customer_id`,`testdb`.`o`.`order_date` AS `order_date`,`testdb`.`c`.`customer_id` AS `customer_id`,`testdb`.`c`.`customer_name` AS `customer_name` from `testdb`.`orders` `o` left join `testdb`.`customers` `c` on(false) where true | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+优化后:开启JOIN消除,执行相同的查询。
SET SESSION join_elimination_mode = 'ON'; EXPLAIN SELECT o.*, c.* FROM orders o LEFT JOIN customers c ON FALSE;SHOW warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `testdb`.`o`.`order_id` AS `order_id`,`testdb`.`o`.`customer_id` AS `customer_id`,`testdb`.`o`.`order_date` AS `order_date`,NULL AS `customer_id`,NULL AS `customer_name` from `testdb`.`orders` `o` | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+执行计划显示
customers表的访问被消除。
场景二:消除不影响结果的唯一键左连接 (LEFT JOIN + UNIQUE)
当左连接的内表未在查询的其他部分被引用,且连接操作不改变外表的行数时,对内表的连接可以被安全地消除。
-- 优化前
SELECT to1.*, to2.*, ..., tom.* FROM to1, to2, ..., tom LEFT JOIN (ti1, ti2, ..., tin) ON cond_on WHERE cond_where ...
-- 优化后
SELECT to1.*, to2.*, ..., tom.* FROM to1, to2, ..., tom WHERE cond_where ...消除条件
内表未被引用:在
LEFT JOIN及其ON条件以外的地方,都没有引用内表(即ti1, ti2, ..., tin)的任何列。连接不影响外表基数或结果:需满足以下条件之一。
连接具有唯一性:对于外表的每一行,经过左连接后有且仅能匹配到内表的一行数据。
连接产生的重复行对结果无影响:即使连接导致外表的行被复制,但查询的后续操作会消除这些重复行的影响。常见情况包括:
连接位于一个
EXISTS或IN子句(即半连接,SEMI JOIN)中。子查询中包含了
GROUP BY、LIMIT或窗口函数等限制条件。递归使用上述两项。
场景三:消除自连接(SELF JOIN)
当一个基表(或其派生表)与自身进行内连接(INNER JOIN)时,相当于用两份相同或相似的数据集进行匹配。如果满足特定条件,优化器可以识别出其中一份数据是多余的,并将其从执行计划中消除,从而避免对同一份数据的重复读取。
基表JOIN基表:
-- 优化前 SELECT target.*, source.* FROM t1 as target JOIN t1 as source WHERE target.uk = source.uk; -- 优化后 SELECT source.*, source.* FROM source WHERE source.uk = source.uk;派生表JOIN派生表:
-- 优化前 SELECT target.*, source.* FROM (SELECT * FROM t1) target JOIN (SELECT * FROM t1 WHERE t1.a > 1) source WHERE target.uk = source.uk; -- 优化后 SELECT source.*, source.* FROM (SELECT * FROM t1 WHERE t1.a > 1) source WHERE source.uk = source.uk;基表JOIN派生表:
-- 优化前 SELECT target.*, source.* FROM t1 target JOIN (SELECT * FROM t1 WHERE t1.a > 1) source WHERE target.uk = source.uk; -- 优化后 SELECT source.*, source.* FROM (SELECT * FROM t1 WHERE t1.a > 1) source WHERE source.uk = source.uk;
消除条件
子集关系:
source表的结果集需是target表结果集的子集。列可用性:查询中所有被引用的
targe表的列,在source表中也需存在。简单来说,被保留的source表需包含查询所需的所有列。唯一键连接:
JOIN的连接条件需是基于target表中的一个唯一键(UNIQUE KEY)或主键(PRIMARY KEY)的等值比较。这个条件是关键,它保证了source表中的每一行最多只能匹配到
target表中的一行。这确保了消除target表不会改变查询结果的行数和逻辑。特殊情况:如果连接条件中没有基于唯一键的等值比较,那么只有当
target表的结果集本身只有0行或1行时,才可能进行消除。
场景四:消除半自连接(SELF SEMI JOIN)
当一个表与自身进行半连接(SEMI JOIN,通常表现为IN或EXISTS子句)时,如果满足特定条件,子查询中的表可以被消除,其条件会被合并到外层查询。
有唯一列相等条件。
-- 优化前 SELECT source.* FROM t1 as source WHERE EXISTS (SELECT * FROM t1 as target WHERE source.uk = target.uk AND target.a > 1); -- 优化后 SELECT source.* FROM t1 as source WHERE source.uk = source.uk AND source.a > 1;无唯一列相等条件。
-- 优化前 SELECT source.* FROM t1 as source WHERE EXISTS (SELECT * FROM t1 as target WHERE source.a = target.a); -- 优化后 SELECT source.* FROM t1 as source WHERE source.a = source.a;
消除条件
子集关系:
source表的结果集需是target表结果集的子集。列可用性:查询中所有被引用的
targe表的列,在source表中也需存在。简单来说,被保留的source表需包含查询所需的所有列。还需满足以下条件之一:
唯一键连接:
JOIN的连接条件需是基于target表中的一个唯一键(UNIQUE KEY)或主键(PRIMARY KEY)的等值比较。这个条件是关键,它保证了source表中的每一行最多只能匹配到
target表中的一行。这确保了消除target表不会改变查询结果的行数和逻辑。特殊情况:如果连接条件中没有基于唯一键的等值比较,那么只有当
target表的结果集本身只有0行或1行时,才可能进行消除。
所有连接条件均为等值(即
)连接,且与其他添加为 AND连接。
场景五:消除外键连接(FOREIGN KEY JOIN)
如果两表间存在外键约束,JOIN条件为该外键关系,且查询只引用子表(含外键的表)的列,则对父表的JOIN可被消除。外键约束保证了子表中的行在父表中必有对应行。
CREATE TABLE target (a int primary key);
CREATE TABLE source (a int, foreign key (a) references target(a));
-- 优化前
SELECT target.a, source.a FROM target, source WHERE target.a = source.a;
-- 优化后
SELECT source.a, source.a FROM source WHERE source.a = source.a;消除条件
父表引用可替代:查询所引用的所有父表列,都可以通过子表的对应外键列来替代(通常意味着只引用了父表的连接键本身)。
连接条件基于外键关系:
JOIN的条件是子表的外键列与父表的主键/唯一键列的等值比较。外键非空保证:子表中的外键列被定义为
NOT NULL,这确保了子表中的每一行在父表中都有确切的对应行。
场景六:消除外键半连接(FOREIGN KEY SEMI JOIN)
当使用EXISTS或IN子句通过外键关系检查父表是否存在对应记录时,该检查是多余的,因为外键约束已保证记录存在。因此,该SEMI JOIN子查询可被消除。
CREATE TABLE target (a int primary key);
CREATE TABLE source (a int, foreign key (a) references target(a));
-- 优化前
SELECT source.a FROM source WHERE EXISTS(SELECT * FROM target WHERE target.a = source.a);
-- 优化后
SELECT source.a FROM source WHERE source.a = source.a;消除条件
父表引用可替代:查询所引用的所有父表列,都可以通过子表的对应外键列来替代(通常意味着只引用了父表的连接键本身)。
连接条件基于外键关系:
JOIN的条件是子表的外键列与父表的主键/唯一键列的等值比较。外键非空保证:子表中的外键列被定义为
NOT NULL,这确保了子表中的每一行在父表中都有确切的对应行。