消除冗余DISTINCT

SQL查询中包含DISTINCT算子时,数据库需要通过排序或哈希操作移除重复行,这会消耗大量的CPU和内存资源,尤其是在处理大规模数据时。在许多场景下,查询结果本身具有唯一性(例如,SELECT列表中包含主键),此时DISTINCT操作是多余的。PolarDB MySQL的优化器通过分析表结构和查询条件中的函数依赖关系,识别并消除冗余的DISTINCT操作。此功能通过减少不必要的计算,降低查询的执行时间、CPU及内存使用率。

工作原理

此优化的核心是利用数据库的函数依赖(Functional Dependency)。函数依赖指表中一列或多列的值能够唯一地决定另一列或多列的值。例如,表的主键(Primary Key)可以唯一确定该表中的任何其他列。

优化器分析带有DISTINCT的查询时,会根据表的元信息(如主键、唯一索引)和查询条件进行推导。如果优化器断定投影列(SELECT列)中的列组合已能保证结果的唯一性,则会将DISTINCT从执行计划中移除。

适用范围

  • 集群版本

    • 产品系列:集群版标准版

    • 内核版本:MySQL 8.0.2,且修订版本需为8.0.2.2.31.1及以上版本。

  • 功能生效范围

    • 投影列包含唯一键:当SELECT语句中的列包含表的唯一键(主键或组成唯一索引的全部列)时,返回结果将确保唯一性。

      -- 假定 user_id 是表 users 的主键
      SELECT DISTINCT user_id, user_name FROM users; -- DISTINCT 可被消除
    • 投影列由唯一键函数决定:当SELECT语句中所有列能够被某一唯一键唯一确定时,查询结果也是唯一的。这种情况通常出现在单表查询或与事实表进行一对一或多对一的关联时。

      -- 假定 user_id 是主键, (user_name, email) 由 user_id 唯一决定
      SELECT DISTINCT user_name, email FROM users WHERE user_id = 123; -- DISTINCT 可被消除
    • 投影列均为常量:当SELECT语句中的所有列均为常量时,结果集最多仅包含一行,此时使用DISTINCT是多余的。优化器将会去除DISTINCT并添加LIMIT 1

      SELECT DISTINCT 'hello', 123 FROM t1 WHERE a = 1; -- DISTINCT 可被消除,并添加 LIMIT 1
    • UNION DISTINCT查询:在进行UNION DISTINCT查询时,如果UNION中的每个SELECT语句自身的结果集已具备唯一性,则优化器能够消除这些内部SELECT语句上的DISTINCT操作。

    说明

    优化器在推导过程中会严格考虑NOT NULL约束。如果唯一键的列允许为NULL,可能会影响唯一性的判断,导致优化器不消除DISTINCT

开启DISTINCT消除功能

通过设置distinct_elimination_mode参数来控制此优化功能的行为。

PolarDB集群参数在控制台与会话中修改方式存在差异,详细区别如下:

  • PolarDB控制台上修改

    • 兼容性说明:部分集群参数在PolarDB控制台上均已添加MySQL配置文件的兼容性前缀loose_

    • 操作方法:找到并修改这些带loose_前缀的参数。

  • 在数据库会话中修改(使用命令行或客户端):

    • 操作方法:当您连接到数据库,使用SET命令修改参数时,请去掉loose_前缀,直接使用参数的原始名称进行修改。

参数名称

级别

描述

loose_distinct_elimination_mode

Global/Session

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

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

  • ON:开启。

  • OFF:关闭。

优化场景示例

要验证DISTINCT消除功能是否生效,您可以使用EXPLAIN命令查看查询的执行计划。如果优化生效,执行计划中将不再出现处理DISTINCT的算子(如排序或哈希去重)。以下为几个典型的优化场景。

场景一:投影列为常量

当优化器识别到投影列为常量时,可消除DISTINCT并添加LIMIT 1

  1. 准备数据:创建t1表并插入数据。

    CREATE TABLE testdb.t1 (a INT, b INT);
    INSERT INTO testdb.t1 VALUES (1, 10), (1, 20);
  2. 执行原始查询

    EXPLAIN SELECT DISTINCT a + 1 FROM testdb.t1 WHERE a = 1;
  3. 验证优化:在优化后的执行计划中,查看warnings中的信息,DISTINCT操作被消除。

    SHOW warnings;
    +-------+------+-------------------------------------------------------------------------------------------------------------------+
    | Level | Code | Message                                                                                                           |
    +-------+------+-------------------------------------------------------------------------------------------------------------------+
    | Note  | 1003 | /* select#1 */ select (`testdb`.`t1`.`a` + 1) AS `a + 1` from `testdb`.`t1` where (`testdb`.`t1`.`a` = 1) limit 1 |
    +-------+------+-------------------------------------------------------------------------------------------------------------------+

场景二:基于函数依赖推导唯一性

当列a唯一决定列b,且b为非NULL的唯一键时,优化器可推导出a也唯一,从而消除DISTINCT

  1. 准备数据:创建t2表,其中b列由a列生成且具有唯一性。

    CREATE TABLE t2 (
      a INT,
      b INT AS (a + 1) UNIQUE
    );
    INSERT INTO t2(a) VALUES (10), (20);
  2. 执行原始查询

    EXPLAIN SELECT DISTINCT a FROM t2 WHERE b IS NOT NULL;
  3. 验证优化:由于b的唯一性保证了a在结果中的唯一性,DISTINCT被移除。查看warnings中的信息:

    SHOW warnings;
    +-------+------+---------------------------------------------------------------------------------------------------------+
    | Level | Code | Message                                                                                                 |
    +-------+------+---------------------------------------------------------------------------------------------------------+
    | Note  | 1003 | /* select#1 */ select `testdb`.`t2`.`a` AS `a` from `testdb`.`t2` where (`testdb`.`t2`.`b` is not null) |
    +-------+------+---------------------------------------------------------------------------------------------------------+

场景三:子查询结果天然唯一

当子查询dt因包含唯一键c而天然唯一时,即使经过JOIN操作,结果的唯一性也可能保持,从而消除外层的DISTINCT

  1. 准备数据:创建t3表,其中c列由ab生成且具有唯一性。

    CREATE TABLE t3 (
      a INT NOT NULL,
      b INT NOT NULL,
      c INT AS (a + b) UNIQUE
    );
    INSERT INTO t3(a, b) VALUES (1, 10), (2, 20);
  2. 执行原始查询

    EXPLAIN SELECT DISTINCT a, b FROM (SELECT a, b, c FROM t3) dt, (SELECT a AS d FROM t3 limit 1) dt2 WHERE c IS NOT NULL;
  3. 验证优化:在优化后的执行计划中,查看warnings中的信息,DISTINCT操作被消除。

    SHOW warnings;                                                                                                 
    +-------+------+----------------------------------------------------------------------------------------------------------------------------------+
    | Level | Code | Message                                                                                                                          |
    +-------+------+----------------------------------------------------------------------------------------------------------------------------------+
    | Note  | 1003 | /* select#1 */ select `testdb`.`t3`.`a` AS `a`,`testdb`.`t3`.`b` AS `b` from `testdb`.`t3` where (`testdb`.`t3`.`c` is not null) |
    +-------+------+----------------------------------------------------------------------------------------------------------------------------------+

性能表现

开启DISTINCT消除功能后,您可以在典型场景中获得显著的性能提升:

指标

性能提升幅度

执行时间

降低10%~60%。

内存使用

降低20%~70%。

CPU占用

减少因哈希或排序操作带来的CPU开销。