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 是實現讀多寫少場景并發控制的強大工具,合理使用可以顯著提高系統吞吐量,特別是在需要協調多個讀取者與少量寫入者的場景中。