PostgreSQL 的 pg_advisory_lock_shared 函数
PostgreSQL 的 pg_advisory_lock_shared 函数详解
pg_advisory_lock_shared 是 PostgreSQL 提供的共享咨询锁函数,允许多个会话同时获取相同键值的共享锁,但排斥排他锁。
共享咨询锁 vs 排他咨询锁
锁类型 | 共享锁 (pg_advisory_lock_shared) | 排他锁 (pg_advisory_lock) |
---|---|---|
并发性 | 允许多个会话同时持有 | 同一时间只能由一个会话持有 |
排斥性 | 排斥排他锁 | 排斥共享锁和排他锁 |
使用场景 | 读多写少场景 | 独占访问场景 |
共享咨询锁函数家族
PostgreSQL 提供以下共享咨询锁相关函数:
函数 | 描述 | 锁类型 |
---|---|---|
pg_advisory_lock_shared(key) | 获取共享会话级咨询锁(阻塞) | 共享锁 |
pg_try_advisory_lock_shared(key) | 尝试获取共享会话级咨询锁(非阻塞) | 共享锁 |
pg_advisory_xact_lock_shared(key) | 获取共享事务级咨询锁(阻塞) | 共享锁 |
pg_try_advisory_xact_lock_shared(key) | 尝试获取共享事务级咨询锁(非阻塞) | 共享锁 |
pg_advisory_unlock_shared(key) | 释放共享会话级咨询锁 | - |
函数详解
1 pg_advisory _lock_shared(key bigint)
功能:获取会话级共享咨询锁(阻塞)
参数:
- key :64位整数锁标识
示例:
-- 会话1获取共享锁
SELECT pg_advisory_lock_shared(123456);-- 会话2可以同时获取相同的共享锁
SELECT pg_advisory_lock_shared(123456);-- 但会话3尝试获取排他锁会被阻塞
SELECT pg_advisory_lock(123456); -- 阻塞直到共享锁释放
2 pg_try_advisory_lock_shared(key bigint)
功能:尝试获取共享会话级咨询锁(非阻塞)
返回值:boolean(true表示获取成功)
示例:
DO $$
BEGINIF pg_try_advisory_lock_shared(123456) THENRAISE NOTICE 'Shared lock acquired, performing read operations...';-- 执行只读操作PERFORM pg_advisory_unlock_shared(123456);ELSERAISE NOTICE 'Could not acquire shared lock';END IF;
END $$;
3 pg_advisory_xact_lock_shared(key bigint)
功能:获取事务级共享咨询锁(事务结束时自动释放)
示例:
BEGIN;
SELECT pg_advisory_xact_lock_shared(123456);
-- 执行只读操作
COMMIT; -- 锁自动释放
锁兼容性矩阵
当前持有锁 \ 请求锁 | 共享锁 | 排他锁 |
---|---|---|
无锁 | 允许 | 允许 |
共享锁 | 允许 | 阻塞 |
排他锁 | 阻塞 | 阻塞 |
实际应用场景
场景1:读写分离控制
-- 读操作使用共享锁
DO $$
BEGINPERFORM pg_advisory_lock_shared(555);-- 多个会话可以同时执行读操作RAISE NOTICE 'Reading data: %', (SELECT count(*) FROM large_table);PERFORM pg_advisory_unlock_shared(555);
EXCEPTION WHEN OTHERS THENPERFORM pg_advisory_unlock_shared(555);RAISE;
END $$;-- 写操作使用排他锁
DO $$
BEGINPERFORM pg_advisory_lock(555); -- 会阻塞直到所有共享锁释放-- 独占执行写操作INSERT INTO large_table VALUES (...);PERFORM pg_advisory_unlock(555);
EXCEPTION WHEN OTHERS THENPERFORM pg_advisory_unlock(555);RAISE;
END $$;
场景2:缓存更新控制
-- 缓存读取(多个客户端可同时读取)
CREATE OR REPLACE FUNCTION get_cached_data(cache_key text) RETURNS json AS $$
DECLAREresult json;
BEGIN-- 获取共享锁(允许并发读取)PERFORM pg_advisory_lock_shared(hashtext(cache_key));SELECT data INTO result FROM cache_table WHERE key = cache_key;PERFORM pg_advisory_unlock_shared(hashtext(cache_key));RETURN result;
END;
$$ LANGUAGE plpgsql;-- 缓存更新(独占访问)
CREATE OR REPLACE FUNCTION update_cache(cache_key text, new_data json) RETURNS void AS $$
BEGIN-- 获取排他锁(阻塞直到所有共享锁释放)PERFORM pg_advisory_lock(hashtext(cache_key));-- 执行更新INSERT INTO cache_table(key, data, updated_at)VALUES (cache_key, new_data, NOW())ON CONFLICT (key) DO UPDATESET data = EXCLUDED.data, updated_at = NOW();PERFORM pg_advisory_unlock(hashtext(cache_key));
END;
$$ LANGUAGE plpgsql;
场景3:配置热加载
-- 配置读取(多个服务实例可同时读取)
CREATE OR REPLACE FUNCTION get_config() RETURNS SETOF config_entry AS $$
BEGIN-- 获取共享锁确保配置加载过程中不被修改PERFORM pg_advisory_lock_shared(1); -- 使用固定键值1表示配置锁RETURN QUERY SELECT * FROM application_config;PERFORM pg_advisory_unlock_shared(1);
END;
$$ LANGUAGE plpgsql;-- 配置更新(管理员调用)
CREATE OR REPLACE FUNCTION reload_config(new_config json) RETURNS void AS $$
BEGIN-- 获取排他锁确保没有服务正在读取配置PERFORM pg_advisory_lock(1);-- 清空并重新加载配置TRUNCATE application_config;INSERT INTO application_configSELECT * FROM json_populate_recordset(NULL::config_entry, new_config);PERFORM pg_advisory_unlock(1);
END;
$$ LANGUAGE plpgsql;
监控共享咨询锁
查看当前共享锁
SELECT pid, locktype, mode, granted
FROM pg_locks
WHERE locktype = 'advisory' AND mode LIKE '%Share%';
查看锁等待情况
SELECT blocked.pid AS blocked_pid,blocking.pid AS blocking_pid,blocked.query AS blocked_query,blocking.query AS blocking_query,blocked.mode AS blocked_mode,blocking.mode AS blocking_mode
FROM pg_catalog.pg_locks blocked
JOIN pg_catalog.pg_stat_activity blocking ON blocking.pid = blocked.blocking_pid
WHERE blocked.locktype = 'advisory' AND NOT blocked.granted;
注意事项
-
锁释放:
- 必须确保每个 pg_advisory_lock_shared() 调用都有对应的 pg_advisory_unlock_shared()
- 事务级共享锁会在事务结束时自动释放
-
死锁风险:
- 共享锁之间不会死锁
- 但共享锁与排他锁混合使用时可能产生死锁
- 建议使用固定的锁获取顺序
-
性能考虑:
- 共享锁比排他锁允许更高的并发性
- 但大量共享锁仍可能影响性能
-
锁粒度:
- 使用不同键值控制不同资源的访问
- 避免使用太少键值导致过度争用
-
会话管理:
- 确保异常情况下锁能被释放(使用EXCEPTION块)
- 长时间持有锁可能导致其他会话长时间等待
pg_advisory_lock_shared 是实现读多写少场景并发控制的强大工具,合理使用可以显著提高系统吞吐量,特别是在需要协调多个读取者与少量写入者的场景中。