文档首页/ 云数据库 TaurusDB/ 内核介绍/ 查询优化/ COUNT(非空列)自动重写优化
更新时间:2026-07-23 GMT+08:00
分享

COUNT(非空列)自动重写优化

操作场景

在SQL语义中,COUNT(column)统计的是column列中非NULL值的行数。当column被定义为NOT NULL时,COUNT(column)等价于COUNT(*)。

TaurusDB能够自动识别这种等价关系,将COUNT(非空列)重写为COUNT(0)(即COUNT(*))。重写后,查询不再依赖该列的数据,因此优化器可以选择不包含该列的索引进行查询。这样既能使用更小的索引,又能避免回表操作,从而显著提升查询性能。

前提条件

  • TaurusDB实例内核版本大于等于2.0.75.260300,支持COUNT非空列优化。内核版本的查询方法请参见如何查看云数据库 TaurusDB实例的版本号
  • COUNT优化开关simplify_count_not_null需处于开启状态(默认开启)。

约束与限制

表1 约束限制

场景

说明

可空列

列定义为可空(DEFAULT NULL或无NOT NULL约束)时,COUNT(列)与COUNT(*)语义不同,不会被重写。

COUNT(非列表达式)

不支持COUNT(a+0)等非列表达式形式,仅支持COUNT(列名)。

COUNT(DISTINCT x)

因为x字段需要参与后续DISTINCT计算,不会被重写。

窗口函数+ROLLUP

当COUNT作为窗口函数且存在ROLLUP时,ROLLUP会使非空列变为可空,此时优化不生效。

参数说明

表2 参数说明

参数

级别

默认值

说明

optimizer_switch -> simplify_count_not_null

SESSION / GLOBAL

ON

控制COUNT(非空列)优化是否开启。设为ON时,自动将COUNT(非空列)重写为COUNT(0)。

使用方法

  • 开启和关闭优化

    通过optimizer_switch参数的simplify_count_not_null选项控制该特性的开启与关闭。详细内容请参见修改TaurusDB实例参数

    您也可以通过SQL语句开启或关闭Count优化。

    • 查看当前状态
      SELECT @@optimizer_switch LIKE '%simplify_count_not_null=on%';
    • 开启优化(默认)
      SET optimizer_switch='simplify_count_not_null=on';
    • 关闭优化
      SET optimizer_switch='simplify_count_not_null=off';
  • 确认优化是否生效

    使用EXPLAIN查看执行计划,可通过以下方式确认优化是否生效:

    • warnings信息:EXPLAIN输出的warnings中显示count(0)表示重写已生效。
    • Using index:Extra列中出现Using index表示使用了索引扫描。
      mysql> CREATE TABLE t1 (a INT NOT NULL, b INT, KEY idx_b(b));
      Query OK, 0 rows affected (0.05 sec)
      mysql> EXPLAIN SELECT COUNT(a) FROM t1 WHERE b > 1 \G
      *************************** 1. row ***************************
                 id: 1
        select_type: SIMPLE
              table: t1
         partitions: NULL
               type: index
      possible_keys: idx_b
                key: idx_b
            key_len: 5
                ref: NULL
               rows: 1
           filtered: 100.00
              Extra: Using where; Using index
      1 row in set, 1 warning (0.01 sec)
      mysql> show warnings;
      +-------+------+-------------------------------------------------------------------------------------------+
      | Level | Code | Message                                                                                   |
      +-------+------+-------------------------------------------------------------------------------------------+
      | Note  | 1003 | /* select#1 */ select count(0) AS `COUNT(a)` from `test`.`t1` where (`test`.`t1`.`b` > 1) |
      +-------+------+-------------------------------------------------------------------------------------------+
      1 row in set (0.01 sec)

      warnings中的`count(0)`表明`COUNT(a)`已被重写为`COUNT(0)`,Extra中的`Using index`表明使用了索引扫描。

    • 您也可以通过optimizer_trace确认重写是否发生。
      mysql> CREATE TABLE t1 (a INT NOT NULL, b INT, KEY idx_b(b));
      Query OK, 0 rows affected (0.07 sec)
      mysql> SET optimizer_trace='enabled=on';
      Query OK, 0 rows affected (0.00 sec)
      mysql> SELECT COUNT(a) FROM t1 WHERE b > 1;
      +----------+
      | COUNT(a) |
      +----------+
      |        0 |
      +----------+
      1 row in set (0.01 sec)
      mysql> SELECT trace LIKE '% count(0) %' FROM information_schema.optimizer_trace;
      +---------------------------+
      | trace LIKE '% count(0) %' |
      +---------------------------+
      |                         1 |
      +---------------------------+
      1 row in set (0.01 sec)

      上述结果表示已发生重写。

      mysql> SET optimizer_trace=default;
      Query OK, 0 rows affected (0.00 sec)

示例1:仅存在单列索引

CREATE TABLE t1 (a INT NOT NULL, b INT, KEY idx_b(b));
INSERT INTO t1 VALUES (1,1),(2,2),(3,3),(5,3),(8,3),(4,-1);
ANALYZE TABLE t1;
  • 优化关闭时:优化器需要读取列a的值来检查是否为NULL,索引idx_b 中不包含 a 列,因此需要回表读取完整行数据来获取a列的值。
    mysql> SET optimizer_switch='simplify_count_not_null=off';
    Query OK, 0 rows affected (0.00 sec)
    mysql> EXPLAIN SELECT COUNT(a) FROM t1 WHERE b > 1 \G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: t1
       partitions: NULL
             type: range
    possible_keys: idx_b
              key: idx_b
          key_len: 5
              ref: NULL
             rows: 1
         filtered: 100.00
            Extra: NULL
    1 row in set, 1 warning (0.00 sec)
  • 优化开启时:COUNT(a) 被重写为 COUNT(0),不再需要访问 a 列的数据。优化器可以选择仅使用 idx_b 索引进行扫描,无需回表操作。
    mysql> SET optimizer_switch='simplify_count_not_null=on';
    Query OK, 0 rows affected (0.00 sec)
    mysql> EXPLAIN SELECT COUNT(a) FROM t1 WHERE b > 1 \G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: t1
       partitions: NULL
             type: index
    possible_keys: idx_b
              key: idx_b
          key_len: 5
              ref: NULL
             rows: 1
         filtered: 100.00
            Extra: Using where; Using index
    1 row in set, 1 warning (0.00 sec)

示例2:同时存在单列索引和复合索引

假设表t1中列a为NOT NULL,同时建有单列索引idx_b和复合索引idx_b_a。

CREATE TABLE t1 (a INT NOT NULL, b INT, KEY idx_b (b), KEY idx_b_a (b, a));
INSERT INTO t1 VALUES (1,1),(2,2),(3,3),(5,3),(8,3),(4,-1);
ANALYZE TABLE t1;
  • 优化关闭时:优化器需要读取列 a 的值来检查是否为 NULL。由于 idx_b 不包含 a 列会导致回表,优化器为了避免回表,会选择复合索引 idx_b_a,直接通过索引完成查询。
    mysql> SET optimizer_switch='simplify_count_not_null=off';
    Query OK, 0 rows affected (0.00 sec)
    mysql> EXPLAIN SELECT COUNT(a) FROM t1 WHERE b > 1 \G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: t1
       partitions: NULL
             type: range
    possible_keys: idx_b,idx_b_a
              key: idx_b_a
          key_len: 5
              ref: NULL
             rows: 4
         filtered: 100.00
            Extra: Using index
    1 row in set, 1 warning (0.00 sec)
  • 优化开启时:COUNT(a) 被重写为 COUNT(0),不再需要访问 a 列的数据。此时优化器可以选择更小的单列索引 idx_b 进行扫描,无需回表,也无需使用更大的复合索引。
    mysql> SET optimizer_switch='simplify_count_not_null=on';
    Query OK, 0 rows affected (0.00 sec)
    mysql> EXPLAIN SELECT COUNT(a) FROM t1 WHERE b > 1 \G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: t1
       partitions: NULL
             type: range
    possible_keys: idx_b,idx_b_a
              key: idx_b
          key_len: 5
              ref: NULL
             rows: 4
         filtered: 100.00
            Extra: Using index
    1 row in set, 1 warning (0.00 sec)

相关文档