KingbaseES 共享锁(SHARE)与排他锁(EXCLUSIVE)详解及测试复现

一、锁的基本概念

1. 什么是共享锁(SHARE)?

定义:共享锁允许多个事务同时读取同一资源,但阻止任何事务修改该资源。

特性:

读共享:多个事务可以同时持有SHARE锁

写互斥:持有SHARE锁时,其他事务不能获取修改类锁(ROW EXCLUSIVE及以上)

自兼容:同一事务可以多次获取SHARE锁

2. 什么是排他锁(EXCLUSIVE)?

定义:排他锁确保只有一个事务可以访问资源,其他事务既不能读也不能写(除了普通的SELECT)。

特性:

独占访问:一次只能有一个事务持有EXCLUSIVE锁

读有限允许:只允许并发的ACCESS SHARE锁(普通SELECT)

自冲突:同一时刻只能有一个EXCLUSIVE锁

二、冲突规则矩阵

完整冲突矩阵

请求的锁模式 当前持有的锁模式 → SHARE 当前持有的锁模式 → EXCLUSIVE
ACCESS SHARE 兼容 兼容
ROW SHARE 兼容 冲突
ROW EXCLUSIVE 冲突 冲突
SHARE UPDATE EXCLUSIVE 冲突 冲突
SHARE 兼容 冲突
SHARE ROW EXCLUSIVE 冲突 冲突
EXCLUSIVE 冲突 冲突
ACCESS EXCLUSIVE 冲突 冲突

关键结论:

SHARE与EXCLUSIVE相互冲突

SHARE锁允许其他SHARE锁和读锁

EXCLUSIVE锁只允许ACCESS SHARE(普通SELECT)

三、测试环境准备脚本

1. 创建测试数据库和表

sql 复制代码
创建测试表
CREATE TABLE shanjiatest_lock (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    balance DECIMAL(10,2)
);

插入测试数据
INSERT INTO shanjiatest_lock VALUES 
(1, 'Alice', 1000.00),
(2, 'Bob', 2000.00),
(3, 'Charlie', 3000.00);

创建索引(用于测试索引锁)
CREATE INDEX idx_shanjiatest_lock_name ON shanjiatest_lock(name);

验证数据
SELECT * FROM shanjiatest_lock ORDER BY id;

2. 创建监控视图

vbnet 复制代码
创建更完整的锁监控视图
CREATE OR REPLACE VIEW lock_monitor AS
SELECT
    l.pid AS 进程ID,
    a.usename AS 用户名,
    a.application_name AS 应用名,
    a.state AS 状态,
    a.wait_event_type AS 等待类型,
    a.wait_event AS 等待事件,
    l.locktype AS 锁类型,
    l.mode AS 锁模式,
    l.granted AS 是否授予,
    CASE l.locktype
        WHEN 'relation' THEN l.relation::regclass::text
        WHEN 'virtualxid' THEN 'Virtual Transaction ID'
        WHEN 'transactionid' THEN 'Transaction ID: ' || l.transactionid::text
        WHEN 'tuple' THEN 'Tuple in ' || l.relation::regclass::text
        WHEN 'page' THEN 'Page in ' || l.relation::regclass::text
        WHEN 'object' THEN 'Object: ' || l.objid::text
        WHEN 'advisory' THEN 'Advisory Lock'
        ELSE 'N/A'
    END AS 对象名,
    a.query AS 当前查询,
    a.xact_start AS 事务开始时间,
    age(now(), a.xact_start) AS 事务时长,
    a.client_addr AS 客户端地址,
    a.client_port AS 客户端端口
FROM sys_locks l
LEFT JOIN sys_stat_activity a ON l.pid = a.pid
WHERE l.pid <> sys_backend_pid()
ORDER BY 
    CASE WHEN l.granted THEN 0 ELSE 1 END, 
    l.pid, 
    l.locktype;


创建阻塞关系视图
CREATE OR REPLACE VIEW blocking_chains AS
SELECT 
    blocked.pid AS 被阻塞进程ID,
    blocked.query AS 被阻塞查询,
    blocked.state AS 被阻塞状态,
    blocking.pid AS 阻塞进程ID,
    blocking.query AS 阻塞查询,
    blocking.state AS 阻塞状态,
    age(now(), blocked.xact_start) AS 阻塞时长
FROM sys_stat_activity blocked
JOIN sys_locks blocked_locks ON blocked.pid = blocked_locks.pid
JOIN sys_locks blocking_locks ON (
    blocked_locks.locktype = blocking_locks.locktype
    AND blocked_locks.database IS NOT DISTINCT FROM blocking_locks.database
    AND blocked_locks.relation IS NOT DISTINCT FROM blocking_locks.relation
    AND blocked_locks.page IS NOT DISTINCT FROM blocking_locks.page
    AND blocked_locks.tuple IS NOT DISTINCT FROM blocking_locks.tuple
    AND blocked_locks.virtualxid IS NOT DISTINCT FROM blocking_locks.virtualxid
    AND blocked_locks.transactionid IS NOT DISTINCT FROM blocking_locks.transactionid
    AND blocked_locks.classid IS NOT DISTINCT FROM blocking_locks.classid
    AND blocked_locks.objid IS NOT DISTINCT FROM blocking_locks.objid
    AND blocked_locks.objsubid IS NOT DISTINCT FROM blocking_locks.objsubid
    AND blocked_locks.pid <> blocking_locks.pid
)
JOIN sys_stat_activity blocking ON blocking_locks.pid = blocking.pid
WHERE NOT blocked_locks.granted
  AND blocking_locks.granted
  AND blocked.pid <> blocking.pid;


四、测试场景1:SHARE锁的基本特性

测试场景1:SHARE锁允许多个读事务共享

会话1:获取SHARE锁

sql 复制代码
BEGIN;
LOCK TABLE shanjiatest_lock IN SHARE MODE;
SELECT '会话1:已获取SHARE锁,当前时间:' || now();
SELECT pg_sleep(10); 

会话2:同时获取SHARE锁

sql 复制代码
BEGIN;
LOCK TABLE shanjiatest_lock IN SHARE MODE NOWAIT;
SELECT '会话2:成功获取SHARE锁,SHARE锁允许多个事务共享';
COMMIT;

会话3:普通SELECT

sql 复制代码
SELECT '会话3:普通SELECT可以执行,返回数据:', COUNT(*) FROM shanjiatest_lock;

会话4:尝试UPDATE

sql 复制代码
BEGIN;
UPDATE shanjiatest_lock SET balance = balance + 100 WHERE id = 1;
SELECT '会话4:UPDATE被SHARE锁阻塞,等待中...';

监控锁状态

sql 复制代码
SELECT * FROM lock_monitor WHERE 对象名 = 'shanjiatest_lock';

进行会话锁清理

会话1回滚释放锁:

ROLLBACK;

会话4的UPDATE现在应该成功

COMMIT;

验证数据

sql 复制代码
SELECT '验证数据:', * FROM shanjiatest_lock ORDER BY id;

如上验证结果:

会话1和会话2可以同时持有SHARE锁

会话3的普通SELECT可以执行

会话4的UPDATE被阻塞,直到会话1释放锁。

总结:(SHARE锁的基本特性(允许多个读事务共享))

会话 操作 结果 验证结论
会话1 LOCK TABLE IN SHARE MODE 成功获取 多个事务可同时持有SHARE锁
会话2 LOCK TABLE IN SHARE MODE 成功获取 SHARE锁自兼容
会话3 普通SELECT 成功执行 允许ACCESS SHARE
会话4 UPDATE 被阻塞 阻止修改操作

五、测试场景2:EXCLUSIVE锁的独占性

会话1:获取EXCLUSIVE锁

sql 复制代码
BEGIN;
LOCK TABLE shanjiatest_lock IN EXCLUSIVE MODE;
SELECT '会话1:已获取EXCLUSIVE锁,当前时间:' || now();
SELECT pg_sleep(10); 

会话2:尝试获取SHARE锁

sql 复制代码
BEGIN;
LOCK TABLE shanjiatest_lock IN SHARE MODE;
SELECT '会话2:SHARE锁被EXCLUSIVE锁阻塞';

会话3:尝试获取另一个EXCLUSIVE锁

sql 复制代码
BEGIN;
LOCK TABLE shanjiatest_lock IN EXCLUSIVE MODE NOWAIT;
SELECT '会话3:EXCLUSIVE锁是自冲突的,无法同时获取';

会话4:普通SELECT

sql 复制代码
SELECT '会话4:普通SELECT可以执行,EXCLUSIVE锁允许ACCESS SHARE';
SELECT COUNT(*) FROM shanjiatest_lock;

会话5:尝试UPDATE

sql 复制代码
BEGIN;
UPDATE shanjiatest_lock SET name = 'David' WHERE id = 2;
SELECT '会话5:UPDATE被EXCLUSIVE锁阻塞';

监控锁状态

vbnet 复制代码
SELECT 
    进程ID,
    用户名,
    锁模式,
    是否授予,
    当前查询,
    age(now(), 事务开始时间) AS 事务时长
FROM lock_monitor 
WHERE 对象名 = 'shanjiatest_lock'
ORDER BY 是否授予 DESC, 进程ID;

使用sys_blocking_pids查看阻塞链

vbnet 复制代码
SELECT 
    pid AS 被阻塞进程,
    sys_blocking_pids(pid) AS 阻塞进程列表,
    query AS 被阻塞查询,
    state AS 状态
FROM sys_stat_activity 
WHERE wait_event_type = 'Lock'
  AND pid <> sys_backend_pid();

进行锁清理

会话1提交释放锁

COMMIT;

会话2、3、5现在应该可以继续

COMMIT; -- 会话2

ROLLBACK; -- 会话3(NOWAIT失败)

COMMIT; -- 会话5

-- 验证最终数据

SELECT '最终数据:', * FROM shanjiatest_lock ORDER BY id;

如上验证结果:

会话1持有EXCLUSIVE锁

会话2的SHARE锁被阻塞

会话3的EXCLUSIVE锁立即失败(NOWAIT)

会话4的普通SELECT成功执行

会话5的UPDATE被阻塞

总结:(EXCLUSIVE锁的独占性)

会话 操作 结果 验证结论
会话1 LOCK TABLE IN EXCLUSIVE MODE 成功获取 独占访问
会话2 LOCK TABLE IN SHARE MODE 被阻塞 与SHARE锁冲突
会话3 LOCK TABLE IN EXCLUSIVE MODE NOWAIT 立即失败 自冲突
会话4 普通SELECT 成功执行 允许ACCESS SHARE
会话5 UPDATE 被阻塞 阻止修改

六、测试场景3:自动加锁行为

1. CREATE INDEX自动加SHARE锁测试

会话1:创建索引(非并发)

sql 复制代码
BEGIN;
SELECT '会话1:开始创建索引,自动获取SHARE锁';
CREATE INDEX idx_shanjiatest_lock_balance ON shanjiatest_lock(balance);
SELECT '会话1:索引创建中,持有SHARE锁...';
SELECT pg_sleep(5);  

会话2:尝试UPDATE(应该被SHARE锁阻塞)

sql 复制代码
BEGIN;
SELECT '会话2:尝试UPDATE,被CREATE INDEX的SHARE锁阻塞';
UPDATE shanjiatest_lock SET name = 'Eve' WHERE id = 3;

监控

sql 复制代码
SELECT * FROM lock_monitor WHERE 锁模式 LIKE '%Share%';

清理

COMMIT; 会话1提交,索引创建完成

COMMIT; 会话2的UPDATE现在完成

2. REFRESH MATERIALIZED VIEW CONCURRENTLY自动加EXCLUSIVE锁

创建物化视图

sql 复制代码
CREATE MATERIALIZED VIEW test_mv AS 
SELECT id, name, balance FROM shanjiatest_lock;

会话1:刷新物化视图(并发方式)

sql 复制代码
BEGIN;
SELECT '会话1:开始刷新物化视图,自动获取EXCLUSIVE锁';
REFRESH MATERIALIZED VIEW CONCURRENTLY test_mv;
SELECT '会话1:刷新中,持有EXCLUSIVE锁...';
SELECT pg_sleep(5);

会话2:尝试获取SHARE锁(应该被阻塞)

sql 复制代码
BEGIN;
SELECT '会话2:尝试获取SHARE锁,被EXCLUSIVE锁阻塞';
LOCK TABLE test_mv IN SHARE MODE;

会话3:普通SELECT物化视图(应该成功)

sql 复制代码
SELECT '会话3:普通SELECT物化视图可以执行';
SELECT COUNT(*) FROM test_mv;

监控SQL:

vbnet 复制代码
SELECT 
    锁类型,
    锁模式,
    是否授予,
    对象名,
    当前查询
FROM lock_monitor 
WHERE 对象名 LIKE 'test_mv%'
ORDER BY 进程ID;

清理锁

COMMIT; -- 会话1

COMMIT; -- 会话2

总结:(自动加锁行为)

操作 自动获取的锁 阻塞情况
CREATE INDEX(非并发) SHARE锁 阻塞UPDATE/DELETE
REFRESH MATERIALIZED VIEW CONCURRENTLY EXCLUSIVE锁 阻塞其他SHARE锁,允许SELECT

七、测试场景4:死锁与锁升级

准备两个测试表

sql 复制代码
DROP TABLE IF EXISTS account_a, account_b;
CREATE TABLE account_a (id INT PRIMARY KEY, balance DECIMAL);
CREATE TABLE account_b (id INT PRIMARY KEY, balance DECIMAL);
INSERT INTO account_a VALUES (1, 1000);
INSERT INTO account_b VALUES (1, 1000);

场景1:锁超时设置

会话1:获取EXCLUSIVE锁并保持

sql 复制代码
BEGIN;
LOCK TABLE account_a IN EXCLUSIVE MODE;
SELECT '会话1:获取EXCLUSIVE锁,将保持30秒';
SELECT pg_sleep(30);

会话2:设置锁超时

sql 复制代码
BEGIN;
SET lock_timeout = '5s';  -- 5秒超时
SELECT '会话2:设置lock_timeout=5s,尝试获取SHARE锁';
LOCK TABLE account_a IN SHARE MODE;  -- 5秒后超时报错
SELECT '会话2:如果看到此消息,说明锁获取成功(不应该出现)';

场景2:NOWAIT选项

会话1保持EXCLUSIVE锁

sql 复制代码
BEGIN;
LOCK TABLE account_b IN EXCLUSIVE MODE;
SELECT '会话1:持有EXCLUSIVE锁';

会话2:使用NOWAIT

sql 复制代码
BEGIN;
SELECT '会话2:使用NOWAIT尝试获取SHARE锁';
LOCK TABLE account_b IN SHARE MODE NOWAIT;  -- 立即失败,不等待
SELECT '会话2:NOWAIT立即返回错误';

清理

COMMIT; -- 会话1

ROLLBACK; -- 会话2

场景3:锁兼容性验证脚本

创建测试函数

sql 复制代码
CREATE OR REPLACE FUNCTION shanjiatest_lock_compatibility(
    lock1_mode TEXT, 
    lock2_mode TEXT
) RETURNS TABLE (
    test_result TEXT,
    lock1_status TEXT,
    lock2_status TEXT,
    is_compatible BOOLEAN
) AS $$
DECLARE
    lock_id BIGINT := 1;  -- 使用一个固定的 advisory lock ID
BEGIN
    -- 会话1获取锁
    PERFORM pg_advisory_lock(lock_id);
    lock1_status := '已授予';
    
    -- 会话2尝试获取锁(使用try锁)
    BEGIN
        IF pg_try_advisory_lock(lock_id) THEN
            -- 成功获取
            test_result := '兼容';
            lock2_status := '已授予';
            is_compatible := true;
            PERFORM pg_advisory_unlock(lock_id);  -- 会话2释放锁
        ELSE
            -- 失败
            test_result := '冲突';
            lock2_status := '未授予';
            is_compatible := false;
        END IF;
    EXCEPTION WHEN OTHERS THEN
        test_result := '冲突';
        lock2_status := '错误';
        is_compatible := false;
    END;
    
    -- 会话1释放锁
    PERFORM pg_advisory_unlock(lock_id);
    
    RETURN QUERY SELECT 
        test_result,
        lock1_status,
        lock2_status,
        is_compatible;
END;
$$ LANGUAGE plpgsql;

-- 测试SHARE和EXCLUSIVE的兼容性

SELECT 'SHARE vs EXCLUSIVE:', * FROM shanjiatest_lock_compatibility('SHARE', 'EXCLUSIVE');

SELECT 'EXCLUSIVE vs SHARE:', * FROM shanjiatest_lock_compatibility('EXCLUSIVE', 'SHARE');

SELECT 'SHARE vs SHARE:', * FROM shanjiatest_lock_compatibility('SHARE', 'SHARE');

SELECT 'EXCLUSIVE vs EXCLUSIVE:', * FROM shanjiatest_lock_compatibility('EXCLUSIVE', 'EXCLUSIVE');

总结:(死锁与锁升级控制)

机制 示例 效果
lock_timeout SET lock_timeout = '5s' 等待5秒后超时报错
NOWAIT LOCK TABLE ... NOWAIT 无法立即获取锁时直接报错

通过以上测试,完整验证了KingbaseES中共享锁与排他锁的特性、冲突规则以及实际应用场景,为数据库锁机制的理解和问题诊断提供了实践基础。

相关推荐
Leo8992 小时前
rust 从零单排 之 一战到底
后端
程序员清风2 小时前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
鱼人3 小时前
MySQL 实战入门:从“增删改查”到“高效查询”的核心指南
后端
大鹏19883 小时前
告别 Session:Spring Boot 实现 JWT 无状态登录认证全攻略
后端
Java编程爱好者3 小时前
从 AQS 到 ReentrantLock:搞懂同步队列与条件队列,这一篇就够了
后端
鱼人3 小时前
Nginx 全能指南:从反向代理到负载均衡,一篇打通任督二脉
后端
UIUV3 小时前
node:child_process spawn 模块学习笔记
javascript·后端·node.js
Java编程爱好者3 小时前
如果明天 Spring 框架突然从世界上消失,Java 会发生什么?
后端
神奇小汤圆4 小时前
Spring让Java慢了30倍,JIT、AOT等让Java比Python快13倍,比C慢17%
后端