当前位置: 首页 > YashanDB, 优化 > 正文

我们的文章会在微信公众号 IT民工的龙马人生 和博客网站 ( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!

由于博客中有大量代码,通过页面浏览效果更佳。

性能实测:YashanDB _CACHE_VARIABLE 开关前后性能相差N倍


摘要

  • 问题:YashanDB 隐藏参数 _CACHE_VARIABLE 文档只写了「是否开启动态常量缓存」,容易被理解成只对 DATE / NUMBER 这类常量有效,VARCHAR 是否受益不清楚。
  • 结论:它控制的是 DETERMINISTIC 函数单条 SQL 执行内部按入参做结果缓存。默认 TRUE。绑定变量、列都可以,不要求整表只有一个值;相同值复用,不同值各算一次VARCHAR2 / CHAR / NVARCHAR2NUMBER / DATE / TIMESTAMP 一样能走缓存;本轮还测到 BOOLEANBINARY_DOUBLERAW、小 CLOB 绑定同样生效。
  • 前后对比(3000 行,入参不变):打开时函数调用 1 次,关闭时 3000 次。函数体内加了固定空循环后,关闭大约 1.04~1.13 秒,打开大约 1 毫秒。3000 行里只有 10 个不同值时,打开只算 10 次,关闭仍是 3000 次。

参数是什么

_CACHE_VARIABLE 是隐藏参数,公开配置视图 V$PARAMETER 里看不到,要查 X$PARAMETER。官方说明只有一句:

是否开启动态常量缓存。

对应能力写在 23.4.1 发行说明的 DETERMINISTIC 函数优化里:输入是字面常量时,直接当常量折叠;输入是绑定变量时,走动态常量缓存,优化和执行都能复用。

它不是数据缓冲、SQL 池、字典缓存。名字里的 VARIABLE 指的是 SQL 执行期的入参(绑定、列值),在这一次执行里按值缓存,不是「只能写死字面量」。

本轮实例上的属性:

NAME             VALUE  DEFAULT  ISSES_MODIFIABLE  ISSYS_MODIFIABLE  ISDEFAULT  ISMODIFIED
_CACHE_VARIABLE  TRUE   TRUE     TRUE              IMMEDIATE         TRUE       FALSE

会话可改,ALTER SYSTEM 为立即生效。隐藏参数属于测试特性,文档要求在原厂指导下使用,线上不要随便改系统级。

文档:


实验环境

版本 YashanDB Enterprise Edition 23.4.14.100 aarch64
部署 单机 lab,监听 10.10.10.x:1788
驱动表 TMP_CV_TCONNECT BY3000
开关方式 ALTER SESSION SET "_CACHE_VARIABLE",测完改回 TRUE
计数方法 DETERMINISTIC 包函数内部 G_CNT + 1,SQL 里对 3000 行做 SUM(f(入参))
耗时 函数内固定空循环约 8000 次(故意放大,便于看墙钟差);DBMS_UTILITY.GET_TIME 单位为百分之一秒

缓存命中时,函数体按 distinct 入参执行,SUM 仍按行数累加缓存结果,业务结果不变。


前后对比

核心 SQL 形态(以 NUMBER 绑定为例,其它类型把函数和绑定类型换掉即可):

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;   -- 或 FALSE
DECLARE
  B NUMBER := 1;
  S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  SELECT SUM(TMP_CV_PKG.F_NUM(B)) INTO S FROM TMP_CV_T;  -- 3000 行
  DBMS_OUTPUT.PUT_LINE('sum=' || S || ' calls=' || TMP_CV_PKG.GET_CNT());
END;
/

函数声明带 DETERMINISTIC,包体内用计数器记录真实执行次数。

调用次数:1 对 3000

场景 _CACHE_VARIABLE=TRUE(开) =FALSE(关) 结果 SUM
NUMBER 字面量 f(1) 1 3000 6000 / 6000
NUMBER 绑定 f(b)b=1 1 3000 6000 / 6000
VARCHAR2 字面量 f('ABC') 1 3000 9000 / 9000
VARCHAR2 绑定 f(b)b='HELLO' 1 3000 15000 / 15000
CHAR 字面量 f('A') 1 3000 3000 / 3000
DATE 字面量 f(DATE '2026-01-01') 1 3000 一致
DATE 绑定 1 3000 一致
TIMESTAMP 字面量 1 3000 一致
TIMESTAMP 绑定 1 3000 一致

SUM 两边一样,只是关掉缓存后函数多算了 2999 次。

原始输出(节选):

NUMBER_LIT ON   sum=6000 calls=1    cs=0
NUMBER_LIT OFF  sum=6000 calls=3000 cs=104

NUMBER_BIND ON  sum=6000 calls=1    cs=0
NUMBER_BIND OFF sum=6000 calls=3000 cs=105

VARCHAR2_LIT ON  sum=9000  calls=1    cs=1
VARCHAR2_LIT OFF sum=9000  calls=3000 cs=103

VARCHAR2_BIND ON  sum=15000 calls=1    cs=1
VARCHAR2_BIND OFF sum=15000 calls=3000 cs=106

CHAR_LIT ON  sum=3000 calls=1    cs=0
CHAR_LIT OFF sum=3000 calls=3000 cs=104

DATE_LIT ON  calls=1    cs=0
DATE_LIT OFF calls=3000 cs=113

DATE_BIND ON  calls=1    cs=0
DATE_BIND OFF calls=3000 cs=104

TIMESTAMP_LIT ON  calls=1    cs=0
TIMESTAMP_LIT OFF calls=3000 cs=104

TIMESTAMP_BIND ON  calls=1    cs=0
TIMESTAMP_BIND OFF calls=3000 cs=104

墙钟时间(yasql TIMING)

空循环是为了让「少算 2999 次」变成肉眼可见的耗时差。便宜的 +1 函数本身墙钟差会很小,但调用次数对比仍然成立。

场景 开(TRUE) 关(FALSE) 大约倍数
NUMBER 字面量 00:00:00.001 00:00:01.048 ~1000×
NUMBER 绑定 00:00:00.001 00:00:01.057 ~1000×
VARCHAR2 字面量 00:00:00.001 00:00:01.038 ~1000×
VARCHAR2 绑定 00:00:00.001 00:00:01.061 ~1000×
CHAR 字面量 00:00:00.001 00:00:01.039 ~1000×
DATE 字面量 00:00:00.001 00:00:01.128 ~1100×
DATE 绑定 00:00:00.001 00:00:01.042 ~1000×
TIMESTAMP 字面量 00:00:00.001 00:00:01.042 ~1000×
TIMESTAMP 绑定 00:00:00.001 00:00:01.043 ~1000×

GET_TIME 分辨率是百分之一秒,打开时常记成 cs=01,和 TIMING 的毫秒级结果一致:打开接近 0,关闭稳定在 1 秒出头。


不只 DATE 和 NUMBER,VARCHAR 可以

可以。本轮在同一套 23.4.14.100 上,相同绑定 / 相同字面量扫 3000 行(补充类型用 500 行)时,下面类型都是「开 = 1 次,关 = 行数次」:

入参类型 是否走动态常量缓存 说明
NUMBER 字面量、绑定都中
VARCHAR2 字面量 'ABC'、绑定 'HELLO' 都中
CHAR
NVARCHAR2 500 行:开 1 / 关 500
DATE 字面量、绑定都中
TIMESTAMP 字面量、绑定都中
BINARY_DOUBLE 绑定 1.5
BOOLEAN 绑定 TRUE
RAW 绑定 HEXTORAW('AABB')
CLOB(小型绑定) 本轮 'HELLO' 绑定开 1 / 关 500

所以不要把它理解成「只给日期和数字用的常量折叠」。机制是:DETERMINISTIC 函数 + 单条 SQL 执行内按入参值缓存,类型是标量(本轮含小 CLOB)都能用。

大 LOB、不同 locator、每行内容都不同的 CLOB 列,不要直接套用这次小绑定的结论。


变量可以,不要求整表同一个值

可以是变量(绑定、列),也可以是字面量。限制不在「是不是变量」,而在「这条 SQL 执行里出现了多少个不同的入参」。

同一条 SELECT SUM(f(x)) FROM t(3000 行,_CACHE_VARIABLE=TRUE):

入参 x 不同值个数 函数实际调用次数
字面量 / 绑定,全程同一个值 1 1
CONST,3000 行全是 1 1 1
GRP = MOD(ID,10) 10 10
TO_CHAR(GRP)(VARCHAR2) 10 10
ID,每行都不同 3000 3000

同一组 10 个不同列值,把参数关掉:

CALLS_10_DISTINCT     = 10     -- TRUE
CALLS_10_DISTINCT_OFF = 3000   -- FALSE

也就是说:打开时按 distinct 值 缓存(10 个值就算 10 次,后面 2990 行直接复用);关掉就按行重算。列、绑定都走这套规则,VARCHAR 同样。

缓存范围是这一次 SQL 执行,不会跨语句、也不会进纯 PL/SQL 调用:

场景 调用次数
一条 SQL 扫 3000 行,绑定始终 b=7 1
循环 10 次 SELECT f(b) FROM DUAL,每次 b 不同 10
循环 10 次 SELECT f(b) FROM DUALb 始终相同 10(语句之间不复用)
PL/SQL 里直接 r := f(7) 调 10 次 10(不走 SQL 这条缓存)

所以:

  • 变量可以f(:b)f(列) 都能缓存。
  • 值可以不同:一条 SQL 里出现 10 个不同入参,就算 10 次,不是只能有一个值。
  • 相同值才会命中:同一个执行里,见过的值才复用;值变了就再算一次。
  • 换一条 SQL 重新开始:即使绑定还是 7,下一次 SELECT ... FROM DUAL 仍会再调函数。

什么时候不会加速

不是「整条 SQL 只算一次」的万能开关。

列值几乎各不相同时,distinct 数接近行数,打开也接近按行调用。本轮 3000 个不同 ID

NUMBER_COL ON    calls=3000 cs=104
VARCHAR2_COL ON  calls=3000 cs=102   -- f(TO_CHAR(ID))

和关掉缓存一个量级。f(主键)f(每行不同的列) 改这个参数没有收益。

另外:

  • 函数必须声明 DETERMINISTIC。相同输入必须相同输出;函数里不要写依赖会话、时间、随机数、DML 的副作用,否则缓存命中会把副作用「吃掉」。
  • 收益取决于 行数 / distinct(入参)。重复度越高越省;绑定或字面量扫大表最极端,相当于 distinct=1。
  • 函数本身极便宜时,墙钟可能看不出差别,调用次数仍会按 distinct 下降。
  • 纯 PL/SQL 调用、以及多次独立 SQL,不吃这条缓存。

怎么查看和开关

-- 隐藏参数只在 X$PARAMETER
SELECT NAME, VALUE, DEFAULT_VALUE,
       ISSES_MODIFIABLE, ISSYS_MODIFIABLE, ISDEFAULT, ISMODIFIED
  FROM X$PARAMETER
 WHERE NAME = '_CACHE_VARIABLE';

对比测试只用会话级,测完改回去:

ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
-- 跑同一条 SQL
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;

默认就是 TRUE,一般保持开启即可。不要为了「试一下」去 ALTER SYSTEM


建议及总结

  1. _CACHE_VARIABLE 默认打开是合理的:确定性函数遇到常量或绑定,少算很多次,结果不变。
  2. VARCHAR 有效,不是 DATE/NUMBER 专属。CHAR、NVARCHAR2、TIMESTAMP 同样有效。
  3. 绑定、列、字面量都可以;看的是一条 SQL 里有多少个不同入参。重复值多就省,f(几乎唯一的列) 不省。
  4. 排查时先看函数是否 DETERMINISTIC、入参的 distinct 数;再用会话级关掉该参数对照。打开时调用次数接近 distinct,关掉接近行数,就可以认定是这条缓存在起作用。
  5. 隐藏参数不当作常规调优旋钮。本轮只改会话、已恢复默认。

附录:复现脚本(lab)

以下为本文实测用过的脚本,在 YashanDB 23.4.14.100 lab 上跑通。仅建议会话级开关;跑完会改回 "_CACHE_VARIABLE"=TRUE 并清理临时对象。生产库勿直接跑。

A. 类型覆盖 + 开关前后对比(3000 行,含 TIMING)

对应正文「前后对比」「VARCHAR 等类型」一节。

-- _CACHE_VARIABLE 类型覆盖 + 开关前后对比
SET SERVEROUTPUT ON
SET FEEDBACK ON
SET TIMING ON

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE TMP_CV_T PURGE';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE TMP_CV_RES PURGE';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
  EXECUTE IMMEDIATE 'DROP PACKAGE TMP_CV_PKG';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/

CREATE TABLE TMP_CV_T AS SELECT LEVEL AS ID FROM DUAL CONNECT BY LEVEL <= 3000;

CREATE TABLE TMP_CV_RES (
  TYP        VARCHAR2(32),
  CACHE_ON   VARCHAR2(8),
  CALLS      NUMBER,
  ELAPSED_CS NUMBER,
  NOTE       VARCHAR2(80)
);

CREATE OR REPLACE PACKAGE TMP_CV_PKG AS
  G_CNT NUMBER := 0;
  PROCEDURE RESET_CNT;
  FUNCTION GET_CNT RETURN NUMBER;
  FUNCTION F_NUM(P NUMBER) RETURN NUMBER DETERMINISTIC;
  FUNCTION F_VC(P VARCHAR2) RETURN NUMBER DETERMINISTIC;
  FUNCTION F_CH(P CHAR) RETURN NUMBER DETERMINISTIC;
  FUNCTION F_DT(P DATE) RETURN NUMBER DETERMINISTIC;
  FUNCTION F_TS(P TIMESTAMP) RETURN NUMBER DETERMINISTIC;
  PROCEDURE BUSY;
END;
/

CREATE OR REPLACE PACKAGE BODY TMP_CV_PKG AS
  PROCEDURE RESET_CNT IS
  BEGIN
    G_CNT := 0;
  END;
  FUNCTION GET_CNT RETURN NUMBER IS
  BEGIN
    RETURN NVL(G_CNT, 0);
  END;
  PROCEDURE BUSY IS
    I NUMBER;
  BEGIN
    I := 1;
    WHILE I <= 8000 LOOP
      I := I + 1;
    END LOOP;
  END;
  FUNCTION F_NUM(P NUMBER) RETURN NUMBER DETERMINISTIC IS
  BEGIN
    G_CNT := NVL(G_CNT, 0) + 1;
    BUSY;
    RETURN NVL(P, 0) + 1;
  END;
  FUNCTION F_VC(P VARCHAR2) RETURN NUMBER DETERMINISTIC IS
  BEGIN
    G_CNT := NVL(G_CNT, 0) + 1;
    BUSY;
    RETURN LENGTH(P);
  END;
  FUNCTION F_CH(P CHAR) RETURN NUMBER DETERMINISTIC IS
  BEGIN
    G_CNT := NVL(G_CNT, 0) + 1;
    BUSY;
    RETURN LENGTH(P);
  END;
  FUNCTION F_DT(P DATE) RETURN NUMBER DETERMINISTIC IS
  BEGIN
    G_CNT := NVL(G_CNT, 0) + 1;
    BUSY;
    RETURN TO_NUMBER(TO_CHAR(P, 'J'));
  END;
  FUNCTION F_TS(P TIMESTAMP) RETURN NUMBER DETERMINISTIC IS
  BEGIN
    G_CNT := NVL(G_CNT, 0) + 1;
    BUSY;
    RETURN EXTRACT(SECOND FROM P);
  END;
END;
/

-- 每种类型:先 TRUE 再 FALSE,记 CALLS / GET_TIME
-- 示例:NUMBER 字面量
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_NUM(1)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('NUMBER_LIT', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'literal 1');
  DBMS_OUTPUT.PUT_LINE('NUMBER_LIT ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_NUM(1)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('NUMBER_LIT', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'literal 1');
  DBMS_OUTPUT.PUT_LINE('NUMBER_LIT OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

-- NUMBER 绑定
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  B NUMBER := 1; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_NUM(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('NUMBER_BIND', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind=1');
  DBMS_OUTPUT.PUT_LINE('NUMBER_BIND ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  B NUMBER := 1; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_NUM(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('NUMBER_BIND', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind=1');
  DBMS_OUTPUT.PUT_LINE('NUMBER_BIND OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

-- VARCHAR2 字面量 / 绑定
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_VC('ABC')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('VARCHAR2_LIT', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'literal ABC');
  DBMS_OUTPUT.PUT_LINE('VARCHAR2_LIT ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_VC('ABC')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('VARCHAR2_LIT', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'literal ABC');
  DBMS_OUTPUT.PUT_LINE('VARCHAR2_LIT OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  B VARCHAR2(20) := 'HELLO'; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_VC(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('VARCHAR2_BIND', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind=HELLO');
  DBMS_OUTPUT.PUT_LINE('VARCHAR2_BIND ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  B VARCHAR2(20) := 'HELLO'; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_VC(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('VARCHAR2_BIND', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind=HELLO');
  DBMS_OUTPUT.PUT_LINE('VARCHAR2_BIND OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

-- CHAR / DATE / TIMESTAMP:同上模式,换 F_CH / F_DT / F_TS
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_CH('A')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('CHAR_LIT', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'literal A');
  DBMS_OUTPUT.PUT_LINE('CHAR_LIT ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_CH('A')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('CHAR_LIT', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'literal A');
  DBMS_OUTPUT.PUT_LINE('CHAR_LIT OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_DT(DATE '2026-01-01')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('DATE_LIT', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'DATE 2026-01-01');
  DBMS_OUTPUT.PUT_LINE('DATE_LIT ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_DT(DATE '2026-01-01')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('DATE_LIT', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'DATE 2026-01-01');
  DBMS_OUTPUT.PUT_LINE('DATE_LIT OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  B DATE := DATE '2026-08-18'; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_DT(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('DATE_BIND', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind DATE');
  DBMS_OUTPUT.PUT_LINE('DATE_BIND ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  B DATE := DATE '2026-08-18'; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_DT(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('DATE_BIND', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind DATE');
  DBMS_OUTPUT.PUT_LINE('DATE_BIND OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_TS(TIMESTAMP '2026-01-01 12:00:00')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('TIMESTAMP_LIT', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'TS literal');
  DBMS_OUTPUT.PUT_LINE('TIMESTAMP_LIT ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_TS(TIMESTAMP '2026-01-01 12:00:00')) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('TIMESTAMP_LIT', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'TS literal');
  DBMS_OUTPUT.PUT_LINE('TIMESTAMP_LIT OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  B TIMESTAMP := TIMESTAMP '2026-08-18 16:00:00'; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_TS(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('TIMESTAMP_BIND', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind TS');
  DBMS_OUTPUT.PUT_LINE('TIMESTAMP_BIND ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE
  B TIMESTAMP := TIMESTAMP '2026-08-18 16:00:00'; T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_TS(B)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('TIMESTAMP_BIND', 'FALSE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'bind TS');
  DBMS_OUTPUT.PUT_LINE('TIMESTAMP_BIND OFF sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

-- 列值几乎各不相同:即使开缓存也接近按行调用
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_NUM(ID)) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('NUMBER_COL', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'column ID 3000 distinct');
  DBMS_OUTPUT.PUT_LINE('NUMBER_COL ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/
DECLARE
  T1 NUMBER; T2 NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  T1 := DBMS_UTILITY.GET_TIME;
  SELECT SUM(TMP_CV_PKG.F_VC(TO_CHAR(ID))) INTO S FROM TMP_CV_T;
  T2 := DBMS_UTILITY.GET_TIME;
  INSERT INTO TMP_CV_RES VALUES ('VARCHAR2_COL', 'TRUE', TMP_CV_PKG.GET_CNT(), T2 - T1, 'TO_CHAR(ID) 3000 distinct');
  DBMS_OUTPUT.PUT_LINE('VARCHAR2_COL ON  sum='||S||' calls='||TMP_CV_PKG.GET_CNT()||' cs='||(T2-T1));
END;
/

SELECT TYP, CACHE_ON, CALLS, ELAPSED_CS, NOTE FROM TMP_CV_RES ORDER BY TYP, CACHE_ON DESC;

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DROP PACKAGE TMP_CV_PKG;
DROP TABLE TMP_CV_RES PURGE;
DROP TABLE TMP_CV_T PURGE;

B. 相同值 / 不同值 / 是否跨语句(3000 行)

对应正文「变量可以,不要求整表同一个值」。

SET SERVEROUTPUT ON
SET FEEDBACK ON

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE TMP_CV_T PURGE';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
  EXECUTE IMMEDIATE 'DROP PACKAGE TMP_CV_PKG';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/

CREATE TABLE TMP_CV_T AS
SELECT LEVEL AS ID,
       MOD(LEVEL, 10) AS GRP,
       1 AS CONST
  FROM DUAL CONNECT BY LEVEL <= 3000;

CREATE OR REPLACE PACKAGE TMP_CV_PKG AS
  G_CNT NUMBER := 0;
  PROCEDURE RESET_CNT;
  FUNCTION GET_CNT RETURN NUMBER;
  FUNCTION F_NUM(P NUMBER) RETURN NUMBER DETERMINISTIC;
  FUNCTION F_VC(P VARCHAR2) RETURN NUMBER DETERMINISTIC;
END;
/
CREATE OR REPLACE PACKAGE BODY TMP_CV_PKG AS
  PROCEDURE RESET_CNT IS BEGIN G_CNT := 0; END;
  FUNCTION GET_CNT RETURN NUMBER IS BEGIN RETURN NVL(G_CNT,0); END;
  FUNCTION F_NUM(P NUMBER) RETURN NUMBER DETERMINISTIC IS
  BEGIN G_CNT := NVL(G_CNT,0)+1; RETURN NVL(P,0); END;
  FUNCTION F_VC(P VARCHAR2) RETURN NUMBER DETERMINISTIC IS
  BEGIN G_CNT := NVL(G_CNT,0)+1; RETURN LENGTH(P); END;
END;
/

SELECT COUNT(*) N, COUNT(DISTINCT ID) D_ID,
       COUNT(DISTINCT GRP) D_GRP, COUNT(DISTINCT CONST) D_CONST
  FROM TMP_CV_T;

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;

-- 1) 列值全相同
BEGIN TMP_CV_PKG.RESET_CNT; END;
/
SELECT SUM(TMP_CV_PKG.F_NUM(CONST)) S FROM TMP_CV_T;
SELECT TMP_CV_PKG.GET_CNT() AS CALLS_SAME_COL FROM DUAL;

-- 2) 10 个不同列值
BEGIN TMP_CV_PKG.RESET_CNT; END;
/
SELECT SUM(TMP_CV_PKG.F_NUM(GRP)) S FROM TMP_CV_T;
SELECT TMP_CV_PKG.GET_CNT() AS CALLS_10_DISTINCT FROM DUAL;

-- 3) 3000 个不同列值
BEGIN TMP_CV_PKG.RESET_CNT; END;
/
SELECT SUM(TMP_CV_PKG.F_NUM(ID)) S FROM TMP_CV_T;
SELECT TMP_CV_PKG.GET_CNT() AS CALLS_3000_DISTINCT FROM DUAL;

-- 4) VARCHAR2 10 个不同值
BEGIN TMP_CV_PKG.RESET_CNT; END;
/
SELECT SUM(TMP_CV_PKG.F_VC(TO_CHAR(GRP))) S FROM TMP_CV_T;
SELECT TMP_CV_PKG.GET_CNT() AS CALLS_VC_10_DISTINCT FROM DUAL;

-- 5) 同一绑定扫 3000 行
DECLARE
  B NUMBER := 7; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  SELECT SUM(TMP_CV_PKG.F_NUM(B)) INTO S FROM TMP_CV_T;
  DBMS_OUTPUT.PUT_LINE('same bind b=7 calls='||TMP_CV_PKG.GET_CNT()||' sum='||S);
END;
/

-- 6) 10 次 SQL,每次绑定不同
DECLARE
  B NUMBER; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  FOR I IN 0..9 LOOP
    B := I;
    SELECT SUM(TMP_CV_PKG.F_NUM(B)) INTO S FROM TMP_CV_T;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE('10 exec different bind calls='||TMP_CV_PKG.GET_CNT());
END;
/

-- 7) 10 次 DUAL,绑定始终相同:语句之间不复用
DECLARE
  B NUMBER := 7; S NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  FOR I IN 1..10 LOOP
    SELECT TMP_CV_PKG.F_NUM(B) INTO S FROM DUAL;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE('10 DUAL same bind calls='||TMP_CV_PKG.GET_CNT());
END;
/

-- 8) 纯 PL/SQL 直接调用:不走这条 SQL 缓存
DECLARE
  R NUMBER;
BEGIN
  TMP_CV_PKG.RESET_CNT;
  FOR I IN 1..10 LOOP
    R := TMP_CV_PKG.F_NUM(7);
  END LOOP;
  DBMS_OUTPUT.PUT_LINE('PLSQL direct same value 10 times calls='||TMP_CV_PKG.GET_CNT());
END;
/

-- 9) 关掉参数:10 个不同值也按行算
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
BEGIN TMP_CV_PKG.RESET_CNT; END;
/
SELECT SUM(TMP_CV_PKG.F_NUM(GRP)) S FROM TMP_CV_T;
SELECT TMP_CV_PKG.GET_CNT() AS CALLS_10_DISTINCT_OFF FROM DUAL;

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DROP PACKAGE TMP_CV_PKG;
DROP TABLE TMP_CV_T PURGE;

C. 补充类型(500 行:NVARCHAR2 / BINARY_DOUBLE / BOOLEAN / RAW / 小 CLOB)

SET SERVEROUTPUT ON
SET FEEDBACK ON

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE TMP_CV_T PURGE';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
  EXECUTE IMMEDIATE 'DROP PACKAGE TMP_CV_PKG';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/

CREATE TABLE TMP_CV_T AS SELECT LEVEL AS ID FROM DUAL CONNECT BY LEVEL <= 500;

-- NVARCHAR2
CREATE OR REPLACE PACKAGE TMP_CV_PKG AS
  G_CNT NUMBER := 0;
  FUNCTION F(P NVARCHAR2) RETURN NUMBER DETERMINISTIC;
END;
/
CREATE OR REPLACE PACKAGE BODY TMP_CV_PKG AS
  FUNCTION F(P NVARCHAR2) RETURN NUMBER DETERMINISTIC IS
  BEGIN G_CNT := NVL(G_CNT,0)+1; RETURN 1; END;
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE S NUMBER; BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(N'ABC')) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('NVARCHAR2 ON calls='||TMP_CV_PKG.G_CNT); END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE S NUMBER; BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(N'ABC')) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('NVARCHAR2 OFF calls='||TMP_CV_PKG.G_CNT); END;
/

-- BINARY_DOUBLE
CREATE OR REPLACE PACKAGE TMP_CV_PKG AS
  G_CNT NUMBER := 0;
  FUNCTION F(P BINARY_DOUBLE) RETURN NUMBER DETERMINISTIC;
END;
/
CREATE OR REPLACE PACKAGE BODY TMP_CV_PKG AS
  FUNCTION F(P BINARY_DOUBLE) RETURN NUMBER DETERMINISTIC IS
  BEGIN G_CNT := NVL(G_CNT,0)+1; RETURN 1; END;
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE S NUMBER; B BINARY_DOUBLE := 1.5;
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('BINARY_DOUBLE ON calls='||TMP_CV_PKG.G_CNT); END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE S NUMBER; B BINARY_DOUBLE := 1.5;
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('BINARY_DOUBLE OFF calls='||TMP_CV_PKG.G_CNT); END;
/

-- BOOLEAN
CREATE OR REPLACE PACKAGE TMP_CV_PKG AS
  G_CNT NUMBER := 0;
  FUNCTION F(P BOOLEAN) RETURN NUMBER DETERMINISTIC;
END;
/
CREATE OR REPLACE PACKAGE BODY TMP_CV_PKG AS
  FUNCTION F(P BOOLEAN) RETURN NUMBER DETERMINISTIC IS
  BEGIN G_CNT := NVL(G_CNT,0)+1; RETURN 1; END;
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE S NUMBER; B BOOLEAN := TRUE;
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('BOOLEAN ON calls='||TMP_CV_PKG.G_CNT); END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE S NUMBER; B BOOLEAN := TRUE;
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('BOOLEAN OFF calls='||TMP_CV_PKG.G_CNT); END;
/

-- RAW
CREATE OR REPLACE PACKAGE TMP_CV_PKG AS
  G_CNT NUMBER := 0;
  FUNCTION F(P RAW) RETURN NUMBER DETERMINISTIC;
END;
/
CREATE OR REPLACE PACKAGE BODY TMP_CV_PKG AS
  FUNCTION F(P RAW) RETURN NUMBER DETERMINISTIC IS
  BEGIN G_CNT := NVL(G_CNT,0)+1; RETURN 1; END;
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE S NUMBER; B RAW(8) := HEXTORAW('AABB');
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('RAW ON calls='||TMP_CV_PKG.G_CNT); END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE S NUMBER; B RAW(8) := HEXTORAW('AABB');
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('RAW OFF calls='||TMP_CV_PKG.G_CNT); END;
/

-- 小 CLOB 绑定
CREATE OR REPLACE PACKAGE TMP_CV_PKG AS
  G_CNT NUMBER := 0;
  FUNCTION F(P CLOB) RETURN NUMBER DETERMINISTIC;
END;
/
CREATE OR REPLACE PACKAGE BODY TMP_CV_PKG AS
  FUNCTION F(P CLOB) RETURN NUMBER DETERMINISTIC IS
  BEGIN G_CNT := NVL(G_CNT,0)+1; RETURN 1; END;
END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DECLARE S NUMBER; B CLOB := 'HELLO';
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('CLOB ON calls='||TMP_CV_PKG.G_CNT); END;
/
ALTER SESSION SET "_CACHE_VARIABLE" = FALSE;
DECLARE S NUMBER; B CLOB := 'HELLO';
BEGIN TMP_CV_PKG.G_CNT:=0; SELECT SUM(TMP_CV_PKG.F(B)) INTO S FROM TMP_CV_T; DBMS_OUTPUT.PUT_LINE('CLOB OFF calls='||TMP_CV_PKG.G_CNT); END;
/

ALTER SESSION SET "_CACHE_VARIABLE" = TRUE;
DROP PACKAGE TMP_CV_PKG;
DROP TABLE TMP_CV_T PURGE;

性能实测:YashanDB _CACHE_VARIABLE 开关前后性能相差N倍:等您坐沙发呢!

发表评论

gravatar

? razz sad evil ! smile oops grin eek shock ??? cool lol mad twisted roll wink idea arrow neutral cry mrgreen

快捷键:Ctrl+Enter