我们的文章会在微信公众号 IT民工的龙马人生 和博客网站 ( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!
由于博客中有大量代码,通过页面浏览效果更佳。
性能实测:YashanDB _CACHE_VARIABLE 开关前后性能相差N倍
摘要
- 问题:YashanDB 隐藏参数
_CACHE_VARIABLE文档只写了「是否开启动态常量缓存」,容易被理解成只对DATE/NUMBER这类常量有效,VARCHAR是否受益不清楚。 - 结论:它控制的是 DETERMINISTIC 函数 在单条 SQL 执行内部按入参做结果缓存。默认
TRUE。绑定变量、列都可以,不要求整表只有一个值;相同值复用,不同值各算一次。VARCHAR2/CHAR/NVARCHAR2与NUMBER/DATE/TIMESTAMP一样能走缓存;本轮还测到BOOLEAN、BINARY_DOUBLE、RAW、小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_T,CONNECT BY 造 3000 行 |
| 开关方式 | 仅 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=0 或 1,和 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 DUAL,b 始终相同 |
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。
建议及总结
_CACHE_VARIABLE默认打开是合理的:确定性函数遇到常量或绑定,少算很多次,结果不变。VARCHAR有效,不是 DATE/NUMBER 专属。CHAR、NVARCHAR2、TIMESTAMP 同样有效。- 绑定、列、字面量都可以;看的是一条 SQL 里有多少个不同入参。重复值多就省,
f(几乎唯一的列)不省。 - 排查时先看函数是否
DETERMINISTIC、入参的 distinct 数;再用会话级关掉该参数对照。打开时调用次数接近 distinct,关掉接近行数,就可以认定是这条缓存在起作用。 - 隐藏参数不当作常规调优旋钮。本轮只改会话、已恢复默认。
附录:复现脚本(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倍:等您坐沙发呢!