锁操作
GaussDB提供了多种锁模式用于控制对表中数据的并发访问。这些模式可以用在MVCC(多版本并发控制)无法给出期望行为的场合。同样,大多数GaussDB命令自动施加恰当的锁,以保证被引用的表在命令的执行过程中不会以一种不兼容的方式被删除或者修改。比如,在存在其他并发操作的时候,ALTER TABLE是不能在同一个表上执行的。
存储过程在执行过程中会获取读锁。当事务提交或会话处于idle、idle in transaction、idle in transaction(aborted)状态时,会释放当前session持有的读锁。
完整的锁操作请参见LOCK。
- 为减少存储过程DDL对并发访问存储过程的阻塞,GaussDB在存储过程变更前使用并发写锁控制并发修改同一对象,在事务提交时使用对象锁阻塞存储过程访问。
- 在高并发DDL场景下可能会偶现死锁报错,用户需要手动重试DDL语句。强烈不推荐在单个事务内执行多条DDL语句。
- 存储过程DDL在事务结束时对存储过程对象加写锁,存储过程错误捕获语句无法捕获DDL死锁。
示例1:
在开启失效重编译参数时,变更表test_tb,会触发级联失效(对依赖对象变更通过锁保护);同时并发访问依赖当前表的对象,触发死锁报错。
时刻1:变更表test_tb,表加写锁。
时刻2:访问pkg1.func,给pkg1加读锁,访问到表test_tb,需要给表test_tb加读锁,触发等待(sess1)。
时刻3:sess1事务提交,需要级联失效依赖对象,需要给pkg1加写锁,触发等待(sess2),死锁报错。
-- 前置数据。
SET DDL_INVALID_MODE='INVALID';
SET ENABLE_FORCE_CREATE_OBJ=ON;
SET BEHAVIOR_COMPAT_OPTIONS='allow_procedure_compile_check';
DROP TABLE IF EXISTS test_tb;
CREATE TABLE test_tb (c1 VARCHAR(2), c2 NUMBER(2));
INSERT INTO test_tb VALUES (1,2);
CREATE OR REPLACE PACKAGE pkg1 IS
CURSOR cur IS SELECT c1,c2 FROM test_tb;
FUNCTION func RETURN TEST_TB.C2%TYPE;
END pkg1;
CREATE OR REPLACE PACKAGE BODY pkg1 IS
FUNCTION func RETURN test_tb.c2%TYPE IS
TYPE ty_tb IS TABLE OF cur%ROWTYPE;
v2 ty_tb;
BEGIN
LOOP
FETCH cur BULK COLLECT INTO v2 LIMIT 10;
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
RETURN '1';
EXCEPTION WHEN OTHERS THEN
RETURN '0';
END;
END pkg1;
/
-- SESSION1 时刻1。
BEGIN;
ALTER TABLE test_tb MODIFY c1 VARCHAR(12);
-- SESSION2 时刻2。
SET BEHAVIOR_COMPAT_OPTIONS='allow_procedure_compile_check';
BEGIN;
CALL pkg1.func();
-- SESSION1 时刻3。
END; --死锁报错。 示例2:
在开启失效重编译参数时,变更表tb_base,会触发级联失效(对依赖对象变更通过锁保护);同时并发访问依赖当前表的对象,触发死锁报错。
时刻1:变更表tb_base,表加写锁。
时刻2:访问proc1,给proc1加读锁,访问到表tb_base,需要给表tb_base加读锁,触发等待(sess1)。
时刻3:sess1事务提交,需要级联失效依赖对象,需要给proc1加写锁,触发等待(sess2),死锁报错。
-- 前置数据。
SET DDL_INVALID_MODE='INVALID';
SET ENABLE_FORCE_CREATE_OBJ=ON;
DROP TABLE IF EXISTS tb_base;
CREATE TABLE tb_base (c1 VARCHAR(2), c2 NUMBER(2));
INSERT INTO tb_base VALUES (1,2);
CREATE OR REPLACE PROCEDURE proc1(p1 tb_base.c1%TYPE, p2 OUT SYS_REFCURSOR) IS
BEGIN
OPEN p2 FOR SELECT c1, c2 FROM tb_base WHERE c1 = p1;
SELECT PG_SLEEP(500);
END;
/
-- SESSION1 时刻1。
BEGIN;
ALTER TABLE tb_base MODIFY c1 VARCHAR(2);
-- SESSION2 时刻2。
BEGIN;
DECLARE
cur SYS_REFCURSOR;
v RECORD;
BEGIN
proc1(1, cur);
FETCH cur INTO v;
CLOSE cur;
RAISE INFO '%', v;
END;
/
END;
-- SESSION1 时刻3。
END; --死锁报错。 示例3:
在开启失效重编译参数时,变更失效对象触发的级联失效与访问失效对象触发的失效重编死锁冲突。
时刻1:变更包ref_pkg1,包加写锁。
时刻2:重编译dep_pkg2,处理默认值时访问ref_pkg1.func(),给ref_pkg1加读锁,发现ref_pkg1包体失效,触发嵌套编译需要给ref_pkg1加写锁,触发等待(sess1)。
时刻3:sess1事务提交,需要级联失效依赖对象,需要给lock_pkg_depend024加写锁,触发等待(sess2),死锁报错。
CREATE OR REPLACE PACKAGE ref_pkg1 AS FUNCTION func RETURN INTEGER; END ref_pkg1; / CREATE OR REPLACE PACKAGE BODY ref_pkg1 AS FUNCTION func RETURN INTEGER AS BEGIN RETURN 1; END; END ref_pkg1; / CREATE OR REPLACE PACKAGE dep_pkg2 AS var1 INTEGER := ref_pkg1.func(); END dep_pkg2; / CREATE OR REPLACE PACKAGE BODY dep_pkg2 AS END dep_pkg2; / -- 构造ref_pkg1D 包体的失效状态。 CREATE OR REPLACE PACKAGE ref_pkg1 AS FUNCTION func RETURN INTEGER; END ref_pkg1; / -- SESSION1 时刻1。 BEGIN; CREATE OR REPLACE PACKAGE ref_pkg1 AS FUNCTION func RETURN INTEGER; END ref_pkg1; / -- SESSION2 时刻2。 ALTER PACKAGE dep_pkg2 COMPILE; -- SESSION1 时刻3。 END; --死锁报错。