PL/pgSQL 全局缓存
本文主要介绍 Vastbase 数据库的 PL/pgSQL 全局缓存功能。
功能描述
Vastbase 在 Oracle 兼容模式下支持 PL/pgSQL 全局缓存,实现在 PL/pgSQL 在编译过程中,在函数本身和某些依赖项没有发生改变的时候(即判断函数/包有效的情况下),编译得到的产物可以在全局重复使用。且全局缓存支持多会话并发使用。
全局缓存判断逻辑
默认情况下,Vastbase 数据库在编译 PL/pgSQL 后会将编译信息缓存在会话中(以下称为会话缓存),有关 Vastbase 数据库会话缓存的判断逻辑,请参见 PL/pgSQL编译缓存。
开启全局缓存后,Vastbase 数据库使用 PL/pgSQL 缓存的判断逻辑发生变化:
1、对于一条 SQL 中复数次使用的函数,Vastbase 数据库会通过存储函数编译信息的方式直接使用,这种情况下不会访问会话缓存。
2、计算调用函数的哈希键,并根据结果查询是否存在会话缓存。
3、若存在会话缓存,则进一步检查缓存的有效性,如果缓存已失效,则删除失效缓存。
4、若不存在会话缓存,或缓存已失效,则根据调用函数的哈希键,查询是否存在全局缓存。
5、检查全局缓存有效性,若有效,则使用全局缓存。
6、若不存在全局缓存,或缓存已失效,则对函数进行编译。
开启全局缓存
PL/pgSQL 全局缓存可通过如下参数控制:
GUC参数 | 参数功能 | 参数介绍 |
---|---|---|
enable_plpgsql_global_compile_cache | 开启全局缓存功能,若开启本参数,则对函数/包进行编译后,将 PLpgSQL 的类型与语法树放入全局缓存。修改重建函数后,其将在下一次检查编译时重新存入全局缓存。 | 参数为实例级别,默认值为off,修改后重启数据库生效。 |
plpgsql_compile_cache_max_size | 用于设置允许全局缓存的大小,当全局缓存达到内存上限时,将会进行缓存替换。 | 参数为实例级别,默认值为100000000,修改后重启数据库生效。 |
查看全局缓存
PL/pgSQL全局缓存可通过如下函数查看相关信息:
函数名称 | 函数功能 | 调用语句 |
---|---|---|
pl_global_compile_cache_entrys | 用于查询所有缓存项目的状态、哈希值、类型等信息。 | select * from pl_global_compile_cache_entrys(); |
pl_global_compile_cache_status | 用于统计当前全局缓存拥有的缓存项数量和占用内存的大小 | select * from pl_global_compile_cache_status(); |
注意事项
该功能仅在数据库兼容模式为 Oracle 时支持(即数据库实例初始化时指定 DBCOMPATIBILITY='A')。
开启全局缓存后,会影响全局范围内第一次执行函数/存储过程的性能。因此建议在多会话场景下开启本功能。有关该特性的示例,请参见 示例 1。
全局缓存仅会获取调用函数过程中编译产生的编译缓存,不会获取创建函数时产生的编译缓存。有关该特性的示例,请参见 示例 2。
示例
示例 1:全局缓存对多会话场景首次执行函数的性能提升。
1、创建测试表,并导入数据。
CREATE TABLE tb_dumpling(
breed VARCHAR2(25),
name VARCHAR2(25),
birthdate DATE);
编辑测试脚本,在 sql 脚本中创建 100 个包,每个包中有一个函数,每个函数中包含一个变量。
vi tb_dumpling.sh
#!/bin/bash
for ((i=1;i<=100;i++))
do
if [ $i -eq 100 ]
then
echo "CREATE OR REPLACE package pkg_dumpling_0 as" >> t_dumpling.sql
echo "FUNCTION f_0 () RETURN VARCHAR ;" >> t_dumpling.sql;
echo "v_0 tb_dumpling%rowtype;">>t_dumpling.sql
echo "TYPE rec_type_0 IS RECORD(" >> t_dumpling.sql
echo "col_int int," >> t_dumpling.sql
echo "col_smallint SMALLINT," >> t_dumpling.sql
echo "col_integer INTEGER," >> t_dumpling.sql
echo "col_bigint BIGINT," >> t_dumpling.sql
echo "col_int2 INT2," >> t_dumpling.sql
echo "col_int4 INT4," >> t_dumpling.sql
echo "col_int8 INT8" >> t_dumpling.sql
echo ");" >> t_dumpling.sql
echo "r_0 rec_type_0;">>t_dumpling.sql
echo "END;" >> t_dumpling.sql
echo "/" >> t_dumpling.sql
else
echo "CREATE OR REPLACE package pkg_dumpling_$i as" >> t_dumpling.sql
echo "FUNCTION f_$i () RETURN VARCHAR ;" >> t_dumpling.sql
echo "v_$i tb_dumpling%rowtype;">>t_dumpling.sql
echo "TYPE rec_type_$i IS RECORD(" >> t_dumpling.sql
echo "col_int int," >> t_dumpling.sql
echo "col_smallint SMALLINT," >> t_dumpling.sql
echo "col_integer INTEGER," >> t_dumpling.sql
echo "col_bigint BIGINT," >> t_dumpling.sql
echo "col_int2 INT2," >> t_dumpling.sql
echo "col_int4 INT4," >> t_dumpling.sql
echo "col_int8 INT8" >> t_dumpling.sql
echo ");" >> t_dumpling.sql
echo "r_$i rec_type_$i;">>t_dumpling.sql
echo "END;" >> t_dumpling.sql
echo "/" >> t_dumpling.sql
fi
done
for ((i=1;i<=100;i++))
do
if [ $i -eq 100 ]
then
echo "CREATE OR REPLACE PACKAGE BODY pkg_dumpling_0 as " >> t_dumpling.sql
echo "FUNCTION f_0 () RETURN VARCHAR " >> t_dumpling.sql;
echo "AS" >> t_dumpling.sql
echo "BEGIN">>t_dumpling.sql
echo "RETURN '123'; " >> t_dumpling.sql
echo "END;" >> t_dumpling.sql
echo "END;" >> t_dumpling.sql
echo "/" >> t_dumpling.sql
else
echo "CREATE OR REPLACE PACKAGE BODY pkg_dumpling_$i as " >> t_dumpling.sql
echo "FUNCTION f_$i () RETURN VARCHAR " >> t_dumpling.sql
echo "AS " >> t_dumpling.sql
echo "BEGIN" >> t_dumpling.sql
echo "v_$i.breed=pkg_dumpling_$((i-1)).f_$((i-1));" >> t_dumpling.sql
echo "r_$i.col_int=10;" >> t_dumpling.sql
echo "dbms_output.put_line(v_$i.breed);" >> t_dumpling.sql
echo "dbms_output.put_line(r_$i.col_int);" >> t_dumpling.sql
echo "RETURN r_$i.col_int;" >> t_dumpling.sql
echo "END;" >> t_dumpling.sql
echo "END;" >> t_dumpling.sql
echo "/" >> t_dumpling.sql
fi
done
2、运行脚本,将 SQL 写入 t_dumpling.sql
文件。
sh tb_dumpling.sh
3、连接 Vastbase 数据库,查看查看执行时间。
vsql -d vastbase -f t_dumpling.sql
在会话 1 中执行以下语句。
\timing
SET max_stack_depth='10MB';
SELECT pkg_dumpling_99.f_99();
返回结果为:
f_99
------
10
(1 row)
Time: 262.737 ms
4、设置参数开启全局缓存功能。
![]()
修改如下参数后应重启数据库使其生效。
ALTER SYSTEM SET plpgsql_compile_cache_max_size=2100000000; ALTER SYSTEM SET enable_plpgsql_global_compile_cache=on;
5、重启数据库后,在会话 2 中执行重新执行以上 SQL。
\timing SET max_stack_depth='10MB'; SELECT pkg_dumpling_99.f_99();
返回结果为:
f_99 ------ 10 (1 row) Time: 321.106 ms
![]()
发现执行时间大于未开启全局缓存的执行时间,这是由于 Vastbase 会将编译缓存拷贝到全局缓存中,以减少其他会话第一次执行的时间。
6、在会话 3 中再次执行以上 SQL。
\timing SET max_stack_depth='10MB'; SELECT pkg_dumpling_99.f_99();
返回结果为:
f_99 ------ 10 (1 row) Time: 89.362 ms
![]()
发现执行时间远小于前一次执行,这是由于全局缓存避免了不同会话间的重复编译。
7、执行上述语句后不退出会话,继续在会话 3 中执行以上 SQL。
\timing SET max_stack_depth='10MB'; SELECT pkg_dumpling_99.f_99();
返回结果为:
f_99 ------ 10 (1 row) Time: 19.401 ms
![]()
发现执行时间与 PLpgSQL编译缓存 中第二次执行时间近似,这是由于存在会话缓存时,Vastbase 数据库会优先获取会话缓存。
8、编写脚本删除测试包与包体。
vi clean_dumpling.sh for ((i=1;i<=100;i++)) do if [ $i -eq 100 ] then echo "DROP PACKAGE BODY pkg_dumpling_0;" >> clean_dumpling.sql else echo "DROP PACKAGE BODY pkg_dumpling_$i;" >> clean_dumpling.sql fi done for ((i=1;i<=100;i++)) do if [ $i -eq 100 ] then echo "DROP PACKAGE pkg_dumpling_0;" >> clean_dumpling.sql else echo "DROP PACKAGE pkg_dumpling_$i;" >> clean_dumpling.sql fi done echo "DROP TABLE tb_dumpling;" >> clean_dumpling.sql
执行清理脚本。
sh clean_dumpling.sh vsql -d vastbase -f clean_dumpling.sql
示例 2:全局缓存获取方式。
1、设置参数开启全局缓存功能。
![]()
修改如下参数后应重启数据库使其生效。
ALTER SYSTEM SET enable_plpgsql_global_compile_cache=1;
2、创建函数,其中在一个函数中调用另一个函数。
CREATE OR REPLACE FUNCTION sp_block7 ( MYINTEGER IN INTEGER , MYCHAR OUT VARCHAR2(200) ) RETURNS VARCHAR2(200) AS $$ DECLARE BEGIN MYCHAR := 'sp_block is called'; RAISE info 'MYINTEGER is %', MYINTEGER; RETURN ; END; $$LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION sp_tempsp7() RETURNS INTEGER AS $$ DECLARE MYINTEGER INTEGER ; MYCHAR VARCHAR2(20); PSV_SQL VARCHAR2(200); BEGIN MYINTEGER := 1; PSV_SQL := 'BEGIN CALL sp_block7(:a,:b);END;'; EXECUTE IMMEDIATE PSV_SQL USING IN MYINTEGER, OUT MYCHAR; RAISE info 'MYCHAR is %', MYCHAR; RETURN 0; END; $$LANGUAGE plpgsql;
3、调用函数。
CALL sp_tempsp7();
返回结果为:
INFO: MYINTEGER is 1 CONTEXT: SQL statement "CALL sp_block7(:a,:b)" PL/pgSQL function inline_code_block line 1 at EXECUTE statement PL/pgSQL function public.sp_tempsp7() line 9 at EXECUTE statement INFO: MYCHAR is sp_block is called sp_tempsp7 ------------ 0 (1 row)
4、查看当前全局缓存函数拥有的缓存项数量和占用内存的大小。
SELECT * FROM pl_global_compile_cache_status();
返回结果为:
total_memory | used_memory | entry_count --------------+-------------+------------- 2100000000 | 0 | 0 (1 row)
![]()
此时没有产生全局缓存,这是由于创建函数时会对函数/存储过程进行编译,因此在同一会话中调用函数/存储过程不会再次编译并产生缓存。而全局缓存仅会获取调用函数时产生的编译缓存,因此此时不能查询到全局缓存。
5、切换会话,重新调用函数,并查看全局缓存(以用户实际情况为准)。
CALL sp_tempsp7(); SELECT * FROM pl_global_compile_cache_status();
返回结果为:
INFO: MYINTEGER is 1 CONTEXT: SQL statement "CALL sp_block7(:a,:b)" PL/pgSQL function inline_code_block line 1 at EXECUTE statement PL/pgSQL function public.sp_tempsp7() line 9 at EXECUTE statement INFO: MYCHAR is sp_block is called sp_tempsp7 ------------ 0 (1 row) vastbase=# total_memory | used_memory | entry_count --------------+-------------+------------- 2100000000 | 239344 | 2 (1 row)
![]()