文档首页/ 云数据库 TaurusDB/ 内核介绍/ 查询优化/ Partial Result Cache(PTRC)
更新时间:2026-01-30 GMT+08:00
分享

Partial Result Cache(PTRC)

操作场景

鉴于结果集缓存对查询性能的增益,我们在TaurusDB引入Partial result cache这一新特性,简称PTRC。顾名思义,这也是一个结果集缓存特性。不同于传统的结果集缓存,PTRC是用来辅助单个查询的内部算子的执行。也就是说PTRC粒度更小,是对查询内部的某个算子的中间结果进行缓存,从而起到算子加速的作用。

PTRC中的Partial有两层含义:

  • PTRC缓存的不是查询的全部数据,而是某个算子(比如Nested loop join, Correlated subquery等)的中间结果集的缓存。
  • 缓存并不一定需要缓存该算子的所有中间结果,而是根据内存大小限制缓存部分数据。

从这两点可以看出,PTRC 与单个查询相关,其生命周期从查询开始至查询结束,并自动终止。由于它用于加速算子,因此一个查询内部可以包含多个 PTRC。只要优化器通过代价计算判断某个算子适合使用 PTRC,则优化器就会为该算子引入PTRC。

工作原理

PTRC的核心原理是针对“参数化的重复扫描”类算子进行加速。此类算子的特点是需要根据外部输入的不同参数值,反复执行相同的扫描或计算逻辑,例如:

  • Nested Loop Join:对于外表扫描的每一行,内表都会根据连接条件进行一次扫描。
  • Correlated Subquery:对于父查询的每一行结果,都会驱动执行一次子查询。

PTRC通过创建以参数值为键(Key)、以对应计算结果为值(Value)的缓存结构来工作。当算子再次执行时,首先尝试根据当前参数值从PTRC中获取结果。若命中,则直接返回缓存值,跳过实际计算;若未命中,则执行原计算逻辑,并将结果存入缓存以供后续使用。以相关子查询为例,其工作流程如下:

图1 PTRC工作流程

控制机制与执行策略

根据PTRC执行流程,引入PTRC并非越多越好,其效果取决于缓存命中率。如果命中率不高,PTRC反而会引入额外的性能开销,例如每次查询都需要检查缓存,以及占用额外的内存空间。为此,我们引入了部分PTRC参数,用于在优化阶段和执行阶段对PTRC的行为进行控制。

引入的PTRC参数,建议保持系统默认值,且不支持在管理控制台修改。如需修改,请提交工单

参数的详细介绍请参考后文详细内容。

  • rds_partial_result_cache_cost_threshold
  • rds_partial_result_cache_max_mem_size
  • rds_partial_result_cache_min_hit_ratio
  • rds_partial_result_cache_hit_ratio_frequency

前提条件

TaurusDB内核版本为2.0.63.250300或以上的版本支持PTRC。内核版本的查询方法请参见如何查看云数据库 TaurusDB实例的版本号

设置PTRC

可以通过参数开关来控制PTRC特性,也可以使用HINT来实现。

  • 通过参数设置

    参数名称

    级别

    描述

    optimizer_switch : partial_result_cache

    Global,Session

    Partial Result Cache功能开关,默认为ON。

    取值范围如下:

    ON :启用Partial Result Cache功能。

    OFF:禁用Partial Result Cache功能。

  • 通过Hint设置

    用户可以通过Hint(提示)在语句级别更精细地控制PTRC(Partial Result Cache)的使用。语句内的Hint优先级高于optimizer_switch系统变量标志,即使全局或会话开关未开启,通过Hint也可以为特定查询启用PTRC。

  • 强制使用PTRC
    • 在查询块中使用,强制该查询块中的所有NLJ算子使用PTRC。
      /*+ PRC_JOIN() */
    • 当嵌套循环连接(NLJ)涉及的所有表(如t1, t2)都包含在Hint列表中时,强制该NLJ算子使用PTRC。
      /*+ PRC_JOIN(t1, t2) */
  • 强制不使用PTRC
    • 在查询块中使用,强制该查询块中的所有NLJ算子不使用PTRC。
      /*+ NO_PRC_JOIN() */
    • 当嵌套循环连接(NLJ)涉及的所有表(如t1, t2)都包含在Hint列表中时,则强制该NLJ算子不使用PTRC。
      /*+ NO_PRC_JOIN(t1, t2) */
  • 强制使用PTRC
    在查询块中使用,强制该查询块中的子查询启用PTRC特性。
    /*+ PRC_SUBQUERY() */

    子查询的PTRC Hint不支持指定表名列表(如/*+ PRC_SUBQUERY(t1)*/)。如果指定,系统会报告警告且该Hint不产生任何效果。

  • 强制不使用PTRC
    在查询块中使用,强制该查询块中的子查询不使用PTRC。
    /*+ NO_PRC_SUBQUERY() */

    子查询的PTRC Hint不支持指定表名列表(如/*+ NO_PRC_SUBQUERY(t1)*/)。如果指定,系统会报告警告且该Hint不产生任何效果。

使用示例

  1. 使用如下任意方式开启PTRC优化特性。
    • 通过SET命令设置此开关值。
      SET optimizer_switch='partial_result_cache=on';
      Query OK, 0 rows affected (0.00 sec)
      SET optimizer_switch='partial_result_cache=off';
      Query OK, 0 rows affected (0.00 sec)
    • 通过HINT方式在SQL语句中设置开关值。
      EXPLAIN FORMAT=TREE SELECT /*+ set_var(optimizer_switch='partial_result_cache=on') */ t1.i AS a, (SELECT t2.i FROM t t2 WHERE t1.j = t2.j ORDER BY j DESC, i DESC LIMIT 1) AS b FROM t t1;
      EXPLAIN FORMAT=TREE SELECT /*+ set_var(optimizer_switch='partial_result_cache=off') */ t1.i AS a, (SELECT t2.i FROM t t2 WHERE t1.j = t2.j ORDER BY j DESC, i DESC LIMIT 1) AS b FROM t t1;
    • 嵌套循环联接(NLJ)场景通过HINT方式在SQL语句中设置强制使用PTRC。
      EXPLAIN FORMAT=TREE SELECT t1.*, (SELECT /*+ PRC_JOIN() */ count(*) FROM (t t2 JOIN t t3) LEFT JOIN (t t4 LEFT JOIN (t t5 JOIN t t6) ON t4.a>t5.a-t1.a) ON t3.a>t2.a-t1.a) FROM t t1;
      EXPLAIN FORMAT=TREE SELECT t1.*, (SELECT /*+ PRC_JOIN(t1,t2,t3) */ count(*) FROM (t t2 JOIN t t3) LEFT JOIN (t t4 LEFT JOIN (t t5 JOIN t t6) ON t4.a>t5.a-t1.a) ON t3.a>t2.a-t1.a) FROM t t1;

      当前语句中包含了多个NLJ算子。若使用提示/*+ PRC_JOIN() */,所有NLJ算子均会被强制使用PTRC。而若使用/*+ PRC_JOIN(t1,t2,t3) */,则仅作用于包含t1、t2、t3表的NLJ算子,这些算子会强制使用PTRC。

    • 嵌套循环联接(NLJ)场景通过HINT方式在SQL语句中设置强制不使用PTRC。
      EXPLAIN FORMAT=TREE SELECT t1.*, (SELECT /*+ NO_PRC_JOIN() */ count(*) FROM (t t2 JOIN t t3) LEFT JOIN (t t4 LEFT JOIN (t t5 JOIN t t6) ON t4.a>t5.a-t1.a) ON t3.a>t2.a-t1.a) FROM t t1;
      EXPLAIN FORMAT=TREE SELECT t1.*, (SELECT /*+ NO_PRC_JOIN(t1,t2,t3) */ count(*) FROM (t t2 JOIN t t3) LEFT JOIN (t t4 LEFT JOIN (t t5 JOIN t t6) ON t4.a>t5.a-t1.a) ON t3.a>t2.a-t1.a) FROM t t1;

      当前语句中包含了多个NLJ算子。当使用提示/*+ NO_PRC_JOIN() */时,所有NLJ算子均被禁止使用PTRC。而若使用/*+ NO_PRC_JOIN(t1,t2,t3) */,则仅作用于包含t1、t2、t3表的NLJ算子,这些算子会被禁止使用PTRC。

    • 相关子查询场景通过HINT方式在SQL语句中设置强制使用PTRC。
      EXPLAIN FORMAT=TREE SELECT t1.i AS a, (SELECT /*+PRC_SUBQUERY() */ t2.i FROM t t2 WHERE t1.j = t2.j ORDER BY j DESC, i DESC LIMIT 1) AS b FROM t t1;
    • 相关子查询场景通过HINT方式在SQL语句中设置强制不使用PTRC。
      EXPLAIN FORMAT=TREE SELECT t1.i AS a, (SELECT /*+NO_PRC_SUBQUERY() */ t2.i FROM t t2 WHERE t1.j = t2.j ORDER BY j DESC, i DESC LIMIT 1) AS b FROM t t1;
  2. 查看控制效果。

    通过执行Explain Analyze/Explain format=tree语句可以确认优化是否生效,执行计划出现'Result cache'关键字时,说明优化生效。

    具体步骤如下:

    1. 准备数据
      CREATE TABLE t (i INTEGER PRIMARY KEY AUTO_INCREMENT, j INTEGER, KEY(j));
      INSERT INTO t VALUES (NULL, NULL);
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      INSERT INTO t SELECT NULL, NULL FROM t;
      ANALYZE TABLE t;
    2. 关闭特性,执行以下SQL语句,优化器选择默认执行计划。
      SET optimizer_switch='partial_result_cache=off';
      Query OK, 0 rows affected (0.00 sec)
      EXPLAIN FORMAT=TREE SELECT t1.i AS a, (SELECT t2.i FROM t t2 WHERE t1.j = t2.j ORDER BY j DESC, i DESC LIMIT 1) AS b FROM t t1\G
      *************************** 1. row ***************************
      EXPLAIN: -> Index scan on t1 using j  (cost=411.60 rows=4096)
      -> Select #2 (subquery in projection; dependent)
          -> Limit: 1 row(s)  (cost=410.97 rows=1)
              -> Index lookup on t2 using j (j=t1.j; iterate backwards)  (cost=410.97 rows=4096)
    3. 在启用特性后,执行以下 SQL 语句,从执行计划中可以看到出现“Result cache”关键字,说明该优化已经生效。cache keys 部分展示了用于构建缓存键的列:
      SET optimizer_switch='partial_result_cache=on';
      Query OK, 0 rows affected (0.00 sec)
      EXPLAIN FORMAT=TREE SELECT t1.i AS a, (SELECT t2.i FROM t t2 WHERE t1.j = t2.j ORDER BY j DESC, i DESC LIMIT 1) AS b FROM t t1\G
      *************************** 1. row ***************************
      EXPLAIN: -> Index scan on t1 using j  (cost=411.60 rows=4096)
      -> Select #2 (subquery in projection; dependent)
          -> Result cache : cache keys(t1.j)
              -> Limit: 1 row(s)  (cost=410.97 rows=1)
                  -> Index lookup on t2 using j (j=t1.j; iterate backwards)  (cost=410.97 rows=4096)

      当执行 EXPLAIN ANALYZE 时,Result cache 部分除了显示作为缓存键的列信息之外,还会输出执行阶段的详细统计信息:

      EXPLAIN ANALYZE SELECT t1.i AS a, (SELECT t2.i FROM t t2 WHERE t1.j = t2.j ORDER BY j DESC, i DESC LIMIT 1) AS b FROM t t1\G
      *************************** 1. row ***************************
      EXPLAIN: -> Index scan on t1 using j  (cost=411.60 rows=4096) (actual time=0.026..0.651 rows=4096 loops=1)
      -> Select #2 (subquery in projection; dependent)
          -> Result cache : cache keys(t1.j) (Cache Hits: 4095, Cache Misses:1, Cache Evictions: 0, Cache Overflows: 0, Memory Usage: 40960 )  (actual time=0.000..0.000 rows=0 loops=4096)
              -> Limit: 1 row(s)  (cost=410.97 rows=1) (actual time=0.000..0.000 rows=0 loops=4096)
                  -> Index lookup on t2 using j (j=t1.j; iterate backwards)  (cost=410.97 rows=4096) (actual time=0.000..0.000 rows=0 loops=4096)
      表1 Result cache指标含义

      指标

      含义

      Cache Hits

      表示缓存命中的次数。

      Cache Misses

      表示缓存未命中的次数。

      Cache Evictions

      表示使用LRU淘汰的记录数。

      Cache Overflows

      表示内存overflow的次数。

      Memory Usage

      表示当前查询使用的内存量。

性能测试

我们采用TPCH中的Q17查询对不同数据规模下PTRC启用前后的性能进行了对比测试。结果表明,引入PTRC后,Q17的查询性能得到显著提升。

以SF 100下的Q17执行计划为例,查询语句中包含相关子查询,该子查询使用了PTRC机制,从执行统计中可见有96.7%(计算公式:Cache Hits/(Cache Hits+Cache Misses))的数据成功命中缓存。由于PTRC本质上是一种缓存结构,命中率越高,带来的性能改善越显著。

EXPLAIN ANALYZE SELECT sum(l_extendedprice) / 7.0 AS avg_yearly FROM LINEITEM, PART WHERE p_PARTkey = l_PARTkey AND p_brand = 'Brand#14' AND p_container = 'SM PKG' AND l_quantity < ( SELECT /*+ PRC_SUBQUERY() */ 0.2 * avg(l_quantity) FROM LINEITEM WHERE l_PARTkey = p_PARTkey ) LIMIT 1;
| EXPLAIN |
| -> Limit: 1 row(s)  (actual time=13338.942..13338.942 rows=1 loops=1)
    -> Aggregate: sum(lineitem.L_EXTENDEDPRICE)  (actual time=13338.941..13338.941 rows=1 loops=1)
        -> Nested loop inner join  (cost=7602572.34 rows=5448536) (actual time=0.369..13331.440 rows=54130 loops=1)
            -> Filter: ((part.P_BRAND = 'Brand#14') and (part.P_CONTAINER = 'SM PKG'))  (cost=2029081.97 rows=197905) (actual time=0.094..8830.363 rows=20004 loops=1)
                -> Table scan on PART  (cost=2029081.97 rows=19790487) (actual time=0.023..7105.163 rows=20000000 loops=1)
            -> Filter: (lineitem.L_QUANTITY < (select #2))  (cost=25.41 rows=28) (actual time=0.198..0.225 rows=3 loops=20004)
                -> Index lookup on LINEITEM using LINEITEM_FK2 (L_PARTKEY=part.P_PARTKEY)  (cost=25.41 rows=28) (actual time=0.117..0.124 rows=30 loops=20004)
                -> Select #2 (subquery in condition; dependent)
                    -> Result cache : cache keys(PART.P_PARTKEY) (Cache Hits: 580430, Cache Misses:20004, Cache Evictions: 0, Cache Overflows: 0, Memory Usage: 4218760 )  (actual time=0.003..0.003 rows=1 loops=600434)
                        -> Aggregate: avg(lineitem.L_QUANTITY)  (actual time=0.003..0.003 rows=0 loops=600434)
                            -> Index lookup on LINEITEM using LINEITEM_FK2 (L_PARTKEY=part.P_PARTKEY)  (cost=28.16 rows=28) (actual time=0.002..0.002 rows=1 loops=600434)
 |

此外,如果PTRC所关联的算子执行代价较高,则通过PTRC所能实现的性能增益也会相应更大。

图2 性能测试结果

相关文档