
# 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中获取结果。若命中，则直接返回缓存值，跳过实际计算；若未命中，则执行原计算逻辑，并将结果存入缓存以供后续使用。以相关子查询为例，其工作流程如下：
图1PTRC工作流程   
![](https://support.huaweicloud.com/kerneldesc-taurusdb/figure/zh-cn_image_0000002516516506.png "点击放大")
#### 控制机制与执行策略
根据PTRC执行流程，引入PTRC并非越多越好，其效果取决于缓存命中率。如果命中率不高，PTRC反而会引入额外的性能开销，例如每次查询都需要检查缓存，以及占用额外的内存空间。为此，我们引入了部分PTRC参数，用于在优化阶段和执行阶段对PTRC的行为进行控制。
![](https://support.huaweicloud.com/kerneldesc-taurusdb/public_sys-resources/caution_3.0-zh-cn.png)
引入的PTRC参数，建议保持系统默认值，且不支持在管理控制台修改。如需修改，请[提交工单](https://console.huaweicloud.com/ticket/?locale=zh-cn#/ticketindex/createIndex)。
参数的详细介绍请参考后文详细内容。
- 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
 
#### 基于代价选择PTRC
在优化阶段，优化器通过估算PTRC的潜在命中率来决定是否为某算子启用该功能。其估算公式为：
命中率 = (算子需重复执行的总次数 - 键的唯一值个数) / 需重复执行的总次数
此公式的本质是计算重复键值的行数占总键值行数的比例。仅当估算出的命中率高于参数"rds_partial_result_cache_cost_threshold "所设定的阈值时，优化器才会为该算子选择使用PTRC，否则将不会启用。
| **参数名称**                                | **级别**         | **描述**                                                                                                                                                                                                                                                                                                                                                                                                                |
|:---|:---|:---|
| rds_partial_result_cache_cost_threshold | Global，Session | 优化器选择partial result cache的命中率门限。如果优化器估算的命中率超过该阈值，partial result cache将会被选择，否则不会被选择。 - 取值范围：\[0-1\]  - 默认值：0.500000   |
   
#### PTRC的动态反馈与禁用
在执行阶段，PTRC会实时统计缓存命中情况。每当累计未命中次数达到"rds_partial_result_cache_hit_ratio_frequency"设定的阈值时，系统会触发命中率检查机制。若实际命中率低于"rds_partial_result_cache_min_hit_ratio"设定的最低要求，优化器将动态禁用后续的PTRC，回退至原始执行方式，以避免因缓存低效带来的额外开销。
这一动态反馈机制能够在执行过程中持续评估PTRC的实际效果，自动判断是否应继续使用该缓存策略。确保仅在实际命中表现符合预期时维持启用，从而在保障查询性能的同时，减少无效缓存操作对执行效率的影响。
| **参数名称**                                     | **级别**         | **描述**                                                                                                                                                                                                                                                                                                                                                                                                                          |
|:---|:---|:---|
| rds_partial_result_cache_min_hit_ratio       | Global，Session | partial result cache执行期间的最小命中率。如果命中率低于该值，partial result cache将被动态关闭。 - 取值范围：\[0-1\]  - 默认值：0.200000                           |
| rds_partial_result_cache_hit_ratio_frequency | Global，Session | partial result cache执行期间检查命中率的频率。如果不能命中的次数达到了该值，partial result cache将会触发检查命中率是否低于最小命中率。 - 取值范围：\[1-4,294,967,295\]  - 默认值：200   |
   
#### PTRC内存管理与淘汰
由于单个查询可以有多个PTRC算子，每个算子都会使用内存来存储缓存数据，那么控制PTRC内存使用就非常有必要，以防止内存OOM。
通过系统变量"rds_partial_result_cache_max_mem_size"来定义每个查询所使用的所有PTRC算子使用的最大内存。如果PTRC使用的内存总数超过该值，优化器会根据LRU算法来进行淘汰。如果通过LRU算法可以找到适合当前存储记录的大小的记录进行淘汰，当前记录可以进行缓存，否则当前记录将不被缓存。
| **参数名称**                              | **级别**         | **描述**                                                                                                                                                                                                                                                                                                                                                                                           |
|:---|:---|:---|
| rds_partial_result_cache_max_mem_size | Global，Session | partial result cache最大允许使用的内存大小。单位：字节 - 取值范围：\[0-18,446,744,073,709,551,615\]  - 默认值：16777216   |
   
#### 前提条件
TaurusDB内核版本为2.0.63.250300或以上的版本支持PTRC。内核版本的查询方法请参见[如何查看云数据库 TaurusDB实例的版本号](https://support.huaweicloud.com/taurusdb_faq/taurusdb_faq_0141.html)。
#### 设置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。
  
 
- [Nested Loop Join场景的PTRC Hint]
  - 强制使用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) */
      ```
      
     
   
- [Correlated Subquery场景的PTRC Hint]
  - 强制使用PTRC
    在查询块中使用，强制该查询块中的子查询启用PTRC特性。
    ```
    /*+ PRC_SUBQUERY() */
    ```
    ![](https://support.huaweicloud.com/kerneldesc-taurusdb/public_sys-resources/caution_3.0-zh-cn.png)
    子查询的PTRC Hint不支持指定表名列表（如/\*+ PRC_SUBQUERY(t1)\*/）。如果指定，系统会报告警告且该Hint不产生任何效果。
    
  
  - 强制不使用PTRC
    在查询块中使用，强制该查询块中的子查询不使用PTRC。
    ```
    /*+ NO_PRC_SUBQUERY() */
    ```
    ![](https://support.huaweicloud.com/kerneldesc-taurusdb/public_sys-resources/caution_3.0-zh-cn.png)
    子查询的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)
      ```
      表1Result cache指标含义 
      | 指标              | 含义               |
      |:---|:---|
      | Cache Hits      | 表示缓存命中的次数。       |
      | Cache Misses    | 表示缓存未命中的次数。      |
      | Cache Evictions | 表示使用LRU淘汰的记录数。   |
      | Cache Overflows | 表示内存overflow的次数。 |
      | Memory Usage    | 表示当前查询使用的内存量。    |
         
      
    
 
#### 开启PTRC不兼容场景说明
当启用PTRC时，缓存中单个Key对应的多行Value数据，其输出顺序可能与原始执行顺序不一致。若查询涉及DOUBLE类型数据的聚合运算，且数据中包含接近该类型边界值的极端数值，这种行顺序的差异可能导致浮点数累加过程中出现中间结果溢出，从而产生不同的最终计算结果。
![](https://support.huaweicloud.com/kerneldesc-taurusdb/figure/zh-cn_image_0000002552830489.png "点击放大")
#### 性能测试
我们采用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性能测试结果   
![](https://support.huaweicloud.com/kerneldesc-taurusdb/figure/zh-cn_image_0000002547956405.png "点击放大")
