连接(JOIN)消除

多表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_前缀,直接使用参数的原始名称进行修改。

参数名称

级别

描述

loose_join_elimination_mode

Global/Session

控制该功能的主开关。取值范围如下:

  • REPLICA_ON(默认值):仅在只读(RO)节点上开启此功能。

  • ON:开启。

  • OFF:关闭。

优化场景示例

当前功能支持在以下六种场景中自动进行连接消除。

场景一:消除ON条件为常假的左连接(LEFT JOIN + FALSE COND)

LEFT JOINON条件恒为FALSE时,该连接无意义。优化器会移除内表,并将其所有列替换为NULL

-- 优化前
SELECT ..., ti1.*, ti2.*, ... FROM ... LEFT JOIN (ti1, ti2, ...) ON FALSE;
-- 优化后
SELECT ..., NULL, NULL, ... FROM ...;

消除条件

LEFT JOINON条件恒为FALSE的表达式(例如1=0FALSE)。

场景示例

  1. 准备环境。

    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');
  2. 优化前:关闭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 |
    +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  3. 优化后:开启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)的任何列。

  • 连接不影响外表基数或结果:需满足以下条件之一。

    • 连接具有唯一性:对于外表的每一行,经过左连接后有且仅能匹配到内表的一行数据。

    • 连接产生的重复行对结果无影响:即使连接导致外表的行被复制,但查询的后续操作会消除这些重复行的影响。常见情况包括:

      • 连接位于一个EXISTSIN子句(即半连接,SEMI JOIN)中。

      • 子查询中包含了GROUP BYLIMIT或窗口函数等限制条件。

      • 递归使用上述两项。

场景三:消除自连接(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,通常表现为INEXISTS子句)时,如果满足特定条件,子查询中的表可以被消除,其条件会被合并到外层查询。

  • 有唯一列相等条件。

    -- 优化前
    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)

当使用EXISTSIN子句通过外键关系检查父表是否存在对应记录时,该检查是多余的,因为外键约束已保证记录存在。因此,该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,这确保了子表中的每一行在父表中都有确切的对应行。

常见问题

开启join_elimination_mod后,为何查询未被优化?

按以下步骤排查:

  1. 检查版本:确认集群版本满足适用范围

  2. 检查场景:确认SQL和表结构符合本文档所述的六种优化场景之一的消除条件。

  3. 检查SELECT列表:对于LEFT JOINFOREIGN KEY JOIN等场景,确保查询的SELECT列表中未引用被消除表的任何列。

  4. 检查查询复杂度:部分复杂的查询结构(如某些子查询嵌套)可能阻止优化器进行消除。可尝试简化查询进行测试。

此功能是否会影响数据一致性或查询结果的正确性?

不会。JOIN消除是在保证查询结果等价的前提下进行的逻辑优化。它只改变查询的执行方式,不改变查询的语义和数据结果。所有优化都经过严格的条件判断,以确保结果正确。