谓词下推

    谓词下推将查询语句中的过滤表达式计算尽可能下推到距离数据源最近的地方,以尽早完成数据的过滤,进而显著地减少数据传输或计算的开销。

    以下通过一些例子对谓词下推优化进行说明,其中示例 1、2、3 为谓词下推适用的案例,示例 4、5、6 为谓词下推不适用的案例。

    在该查询中,将谓词 下推到 TiKV 上对数据进行过滤,可以减少由于网络传输带来的开销。

    示例 2: 谓词下推到存储层

    1. create table t(id int primary key, a int not null);
    2. explain select * from t where a < substring('123', 1, 1);
    3. +-------------------------+----------+-----------+---------------+--------------------------------+
    4. | id | estRows | task | access object | operator info |
    5. | TableReader_7 | 3323.33 | root | | data:Selection_6 |
    6. | └─Selection_6 | 3323.33 | cop[tikv] | | lt(test.t.a, 1) |
    7. | └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo |
    8. +-------------------------+----------+-----------+---------------+--------------------------------+

    该查询与示例 1 中的查询生成了完成一样的执行计划,这是因为谓词 a < substring('123', 1, 1)substring 的入参均为常量,因此可以提前计算,进而简化得到等价的谓词 a < 1。进一步的,可以将 a < 1 下推至 TiKV 上。

    此外,这条 SQL 执行的是内连接,且 ON 条件是 t.a = s.a,可以由 推导出谓词 s.a < 1,并将其下推至 join 运算前对 s 表进行过滤,可以进一步减少 join 时的计算开销。

    示例 4: 存储层不支持的谓词无法下推

    1. create table t(id int primary key, a int not null);
    2. desc select * from t where substring('123', a, 1) = '1';
    3. +-------------------------+---------+-----------+---------------+----------------------------------------+
    4. | id | estRows | task | access object | operator info |
    5. +-------------------------+---------+-----------+---------------+----------------------------------------+
    6. | Selection_7 | 2.00 | root | | eq(substring("123", test.t.a, 1), "1") |
    7. | └─TableReader_6 | 2.00 | root | | data:TableFullScan_5 |
    8. +-------------------------+---------+-----------+---------------+----------------------------------------+

    在该查询中,存在谓词 substring('123', a, 1) = '1'

    从 explain 结果中可以看到,该谓词没有被下推到 TiKV 上进行计算,这是因为 TiKV coprocessor 中没有对 substring 内置函数进行支持,因此无法将其下推到 TiKV 上。

    在该查询中,内表 s 上存在谓词 s.a is null

    示例 6: 谓词中包含用户变量时不能下推

    1. create table t(id int primary key, a char);
    2. explain select * from t where a < @a;
    3. +-------------------------+----------+-----------+---------------+--------------------------------+
    4. | id | estRows | task | access object | operator info |
    5. +-------------------------+----------+-----------+---------------+--------------------------------+
    6. | Selection_5 | 8000.00 | root | | lt(test.t.a, getvar("a")) |
    7. | └─TableReader_7 | 10000.00 | root | | data:TableFullScan_6 |
    8. | └─TableFullScan_6 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo |
    9. +-------------------------+----------+-----------+---------------+--------------------------------+

    在该查询中,表 t 上存在谓词 a < @a,其中 @a 为值为 1 的用户变量。

    从 explain 中可以看到,该谓词没有像示例 2 中一样,将谓词简化为 a < 1 并下推到 TiKV 上进行计算。这是因为,用户变量 @a 的值可能会某些场景下在查询过程中发生改变,且 TiKV 对于用户变量 @a 的值不可知,因此 TiDB 不会将 @a 替换为 1,且不会下推至 TiKV 上进行计算。

    一个帮助理解的例子如下:

    可以从在该查询中看到,@a 的值会在查询过程中发生改变,因此如果将 替换为 a = 1 并下推至 TiKV,则优化前后不等价。