共享锁 (Shared Lock / S Lock)
📋 概述
共享锁(Shared Lock),又称读锁(Read Lock)或 S 锁,是一种允许多个事务同时读取同一数据资源的锁机制。当一个事务获得某资源的共享锁后,其他事务也可以获得该资源的共享锁,但不能获得排他锁。
核心特点
✅ 优点:
- 高并发读: 多个事务可以同时读取同一资源
- 读写分离: 支持读写分离架构
- 数据一致性: 确保读取期间数据不被修改
- 无死锁风险: 纯共享锁不会导致死锁
❌ 缺点:
- 阻塞写操作: 持有共享锁时会阻止写操作
- 可能升级为排他锁: 需要修改时需重新获取锁
- 长事务影响: 长时间持有会阻塞写操作
- 快照读vs当前读: 不同数据库实现有差异
适用场景
- 一致性读: 需要确保读取期间数据不变
- 报表查询: 大量并发的只读操作
- 数据验证: 读取后基于数据进行决策
- 读写分离: 从库上的查询操作
- 引用完整性检查: 外键约束检查
🔧 工作原理
锁兼容性矩阵
共享锁的核心特性体现在其兼容性上:
| 无锁 | S锁 | X锁
--------|-------|-------|------
S锁 | ✅ | ✅ | ❌
X锁 | ✅ | ❌ | ❌解读:
- ✅ = 兼容,可以同时持有
- ❌ = 冲突,必须等待
示例:
事务A: SELECT ... LOCK IN SHARE MODE; -- 获得 S 锁
事务B: SELECT ... LOCK IN SHARE MODE; -- ✅ 可以获得 S 锁
事务C: UPDATE ... -- ❌ 必须等待事务A释放 S 锁锁的获取与释放
显式获取
sql
-- MySQL
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- PostgreSQL
SELECT * FROM users WHERE id = 1 FOR SHARE;
-- SQL Server
SELECT * FROM users WITH (HOLDLOCK, ROWLOCK) WHERE id = 1;
-- Oracle
-- Oracle 没有直接的共享锁语法,使用 MVCC自动获取
某些操作会自动获取共享锁:
| 操作 | 说明 |
|---|---|
SELECT (某些隔离级别) | 可串行化隔离级别 |
| 外键检查 | 检查引用完整性时 |
CREATE INDEX | 创建索引时 |
| 某些 DDL 操作 | 需要读取表结构时 |
锁的释放
sql
-- 共享锁在事务结束时自动释放
BEGIN;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- ... 其他操作
COMMIT; -- 或 ROLLBACK,自动释放 S 锁💻 各数据库实现
MySQL
MySQL InnoDB 引擎支持显式的共享锁。
基本用法
sql
-- 显式加共享锁
BEGIN;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 其他事务也可以加共享锁读取
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- ✅ 允许
-- 但更新会被阻塞
UPDATE users SET name = 'test' WHERE id = 1; -- ❌ 阻塞
COMMIT;
-- 共享锁也适用于多行
SELECT * FROM users WHERE age > 18 LOCK IN SHARE MODE;快照读 vs 当前读
MySQL InnoDB 有两种读取方式:
1. 快照读 (Snapshot Read)
- 普通的
SELECT语句 - 使用 MVCC 读取历史版本
- 不加锁
- 看到的数据可能是旧版本
sql
-- 快照读,不加锁
SELECT * FROM users WHERE id = 1;2. 当前读 (Current Read)
SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATEUPDATE,DELETE,INSERT- 加锁
- 看到最新提交的数据
sql
-- 当前读,加共享锁
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;查看共享锁
sql
-- 查看当前的共享锁
SELECT
lock_id,
lock_trx_id,
lock_mode,
lock_type,
lock_table,
lock_index,
lock_data
FROM performance_schema.data_locks
WHERE lock_mode = 'S'; -- S 表示共享锁
-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;PostgreSQL
PostgreSQL 提供更细粒度的共享锁控制。
锁模式
PostgreSQL 有多种共享锁模式:
sql
-- FOR SHARE - 标准共享锁
SELECT * FROM users WHERE id = 1 FOR SHARE;
-- FOR KEY SHARE - 最弱的锁,不锁定非关键列
SELECT * FROM users WHERE id = 1 FOR KEY SHARE;
-- FOR NO KEY UPDATE - 比 FOR UPDATE 弱
SELECT * FROM users WHERE id = 1 FOR NO KEY UPDATE;锁兼容性
请求模式 | FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE
------------------|---------------|-----------|-------------------|----------
FOR KEY SHARE | ✓ | ✓ | ✓ | ✓
FOR SHARE | ✓ | ✓ | ✓ | ✗
FOR NO KEY UPDATE | ✓ | ✓ | ✗ | ✗
FOR UPDATE | ✓ | ✗ | ✗ | ✗查看共享锁
sql
-- 查看所有锁
SELECT
l.locktype,
l.relation::regclass AS table_name,
l.mode,
l.granted,
a.pid,
a.usename,
a.query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE l.mode LIKE '%SHARE%'
ORDER BY l.relation;SQL Server
SQL Server 通过提示(Hints)来控制共享锁。
基本用法
sql
-- 使用共享锁提示
SELECT * FROM users WITH (ROWLOCK, SHARELOCK) WHERE id = 1;
-- 保持共享锁到事务结束
BEGIN TRANSACTION;
SELECT * FROM users WITH (HOLDLOCK, ROWLOCK) WHERE id = 1;
-- HOLDLOCK 等同于 SERIALIZABLE 隔离级别
-- 共享锁会保持到事务结束
COMMIT TRANSACTION;
-- 表级共享锁
SELECT * FROM users WITH (TABLOCK) WHERE id = 1;隔离级别的影响
sql
-- READ COMMITTED (默认)
-- SELECT 不加锁,使用行版本控制
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- REPEATABLE READ
-- SELECT 加共享锁,保持到事务结束
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- SERIALIZABLE
-- SELECT 加范围共享锁
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;查看共享锁
sql
-- 查看当前的共享锁
SELECT
request_session_id,
resource_type,
resource_database_id,
resource_associated_entity_id,
request_mode,
request_status
FROM sys.dm_tran_locks
WHERE request_mode IN ('S', 'IS'); -- S=共享锁, IS=意向共享锁Oracle
Oracle 主要通过 MVCC 实现读一致性,较少使用传统共享锁。
MVCC 机制
sql
-- Oracle 的 SELECT 不加锁
SELECT * FROM users WHERE id = 1;
-- 通过 undo 日志读取一致性版本
-- 如果需要显式锁定,通常使用排他锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;表级共享锁
sql
-- 显式锁定表
LOCK TABLE users IN SHARE MODE;
-- 阻止其他事务获取排他锁
-- 但仍允许其他事务获取共享锁查看锁
sql
-- 查看共享锁
SELECT
s.sid,
s.serial#,
s.username,
l.type,
l.lmode,
l.request,
o.object_name
FROM v$session s
JOIN v$lock l ON s.sid = l.sid
JOIN dba_objects o ON l.id1 = o.object_id
WHERE l.type = 'TM' -- TM = DML/DDL 锁
AND l.lmode = 4; -- 4 = Share mode📊 性能影响分析
并发度评估
| 场景 | 并发度 | 说明 |
|---|---|---|
| 多个 S 锁 | ⭐⭐⭐⭐⭐ | 完全兼容 |
| S 锁 + X 锁 | ⭐ | 互相阻塞 |
| 长事务 S 锁 | ⭐⭐ | 阻塞写操作 |
| 短事务 S 锁 | ⭐⭐⭐⭐⭐ | 几乎无影响 |
性能测试示例
sql
-- 测试场景:100个并发事务读取同一行
-- 使用共享锁
-- 100个事务可以并行读取,总耗时:~0.1秒
BEGIN;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
COMMIT;
-- 如果有一个事务持有排他锁
-- 100个事务需要串行等待,总耗时:~50秒🎯 最佳实践
✅ 推荐做法
1. 短时间持有共享锁
sql
-- ✅ 推荐:快速读取并提交
BEGIN;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 立即处理数据
COMMIT;
-- ❌ 不推荐:长时间持有
BEGIN;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 执行大量无关操作
SLEEP(60);
COMMIT;2. 使用合适的隔离级别
sql
-- 读已提交(大多数场景)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 可重复读(需要更强一致性)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 可串行化(最强,但性能最差)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;3. 避免不必要的共享锁
sql
-- ❌ 不推荐:普通读取不需要共享锁
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- ✅ 推荐:普通 SELECT 使用快照读
SELECT * FROM users WHERE id = 1;4. 读写分离架构
sql
-- 主库:写操作
UPDATE users SET name = 'test' WHERE id = 1;
-- 从库:读操作(使用共享锁或不加锁)
SELECT * FROM users WHERE id = 1;❌ 避免陷阱
1. 避免共享锁导致的写阻塞
sql
-- ❌ 危险:长时间持有共享锁阻塞写
BEGIN;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 执行大量操作(30秒)
COMMIT;
-- 这期间所有 UPDATE/DELETE 都被阻塞
-- ✅ 推荐:快速完成
BEGIN;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 快速处理(< 1秒)
COMMIT;2. 避免忘记提交事务
python
# ❌ 危险:忘记提交
cursor.execute("SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE")
# 忘记 commit(),锁一直持有
# ✅ 正确:使用上下文管理器
with connection.begin():
cursor.execute("SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE")
# 自动提交或回滚3. 避免在循环中使用共享锁
sql
-- ❌ 性能差
FOR i IN 1..1000 LOOP
SELECT * FROM users WHERE id = i LOCK IN SHARE MODE;
END LOOP;
-- ✅ 批量读取
SELECT * FROM users WHERE id BETWEEN 1 AND 1000 LOCK IN SHARE MODE;🔍 监控与诊断
MySQL 监控
sql
-- 查看共享锁
SELECT
lock_id,
lock_trx_id,
lock_mode,
lock_type,
lock_table,
lock_data
FROM performance_schema.data_locks
WHERE lock_mode = 'S';
-- 查看锁等待
SELECT
requesting_thread_id,
blocking_thread_id,
wait_age
FROM performance_schema.data_lock_waits;
-- 查看长时间运行的事务
SELECT * FROM information_schema.innodb_trx
WHERE trx_state = 'RUNNING'
AND TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 10;PostgreSQL 监控
sql
-- 查看共享锁
SELECT
l.relation::regclass AS table_name,
l.mode,
l.granted,
a.pid,
a.query,
now() - a.query_start AS duration
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE l.mode LIKE '%SHARE%'
ORDER BY duration DESC;SQL Server 监控
sql
-- 查看共享锁
SELECT
request_session_id,
OBJECT_NAME(resource_associated_entity_id) AS table_name,
request_mode,
request_status
FROM sys.dm_tran_locks
WHERE request_mode IN ('S', 'IS');🚨 常见问题
Q1: 共享锁和快照读有什么区别?
答:
| 特性 | 共享锁 (当前读) | 快照读 |
|---|---|---|
| 语法 | SELECT ... LOCK IN SHARE MODE | SELECT |
| 加锁 | ✅ 是 | ❌ 否 |
| 数据版本 | 最新 | 历史快照 |
| 阻塞写 | ✅ 是 | ❌ 否 |
| 并发度 | 较低 | 较高 |
| 一致性 | 强 | 弱 |
Q2: 共享锁会导致死锁吗?
答:
纯共享锁不会死锁,因为多个 S 锁互相兼容。
但如果涉及锁升级(S → X),则可能死锁:
sql
-- 事务 A
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- S 锁
UPDATE users SET name = 'A' WHERE id = 1; -- 尝试升级为 X 锁
-- 事务 B
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- S 锁
UPDATE users SET name = 'B' WHERE id = 1; -- 尝试升级为 X 锁
-- 结果:死锁!两个事务都在等待对方释放 S 锁解决: 直接使用 FOR UPDATE 获取 X 锁。
Q3: 何时应该使用共享锁?
答:
推荐使用场景:
- 需要确保读取期间数据不被修改
- 读取后要基于数据做决策
- 外键约束检查
- 可串行化隔离级别要求
不推荐使用场景:
- 普通查询(使用快照读)
- 高并发写场景
- 长事务
Q4: 共享锁的性能开销大吗?
答:
开销较小,但需要注意:
- 内存开销: 每个锁对象占用少量内存
- 管理开销: 锁表的维护成本
- 阻塞开销: 可能阻塞写操作
建议:
- 短时间持有
- 避免不必要的共享锁
- 优先使用快照读
📚 相关资源
内部链接
外部资源
最后更新: 2026-04-12
维护状态: ✅ 完整