read_committed_snapshot通过让select读取tempdb中的行版本而非加s锁,解耦读写路径,减少锁等待;需单用户模式下启用,注意tempdb压力及写-写冲突仍存在。

READ_COMMITTED_SNAPSHOT 为什么能减少锁等待
默认的 READ COMMITTED 隔离级别下,每个 SELECT 都会加共享锁(S 锁),读完立刻释放;但 UPDATE 过程中先加更新锁(U 锁),再升级为排他锁(X 锁)——这意味着读写之间会互相阻塞。开启行版本控制后,SELECT 不再申请 S 锁,而是从 tempdb 的版本存储区读取事务开始时的快照数据,写操作仍照常加 X 锁,但读不抢资源了。
关键点在于:它不改变写行为,只解耦读路径。高频 UPDATE 场景下,大量并发 SELECT 不再排队等锁,tempdb 承担了版本复制开销,换来的是锁争用下降和响应时间稳定。
如何安全启用 READ_COMMITTED_SNAPSHOT
必须逐库启用,且不能在事务中执行。常见错误是直接运行 ALTER DATABASE [db] SET READ_COMMITTED_SNAPSHOT ON 后发现没生效——因为该选项需要数据库处于单用户模式或无活跃连接状态。
- 先检查当前连接:
SELECT session_id, login_name, status FROM sys.dm_exec_sessions WHERE database_id = DB_ID('your_db') - 踢掉活跃会话(生产环境慎用):
ALTER DATABASE [your_db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE - 启用版本控制:
ALTER DATABASE [your_db] SET READ_COMMITTED_SNAPSHOT ON - 恢复多用户:
ALTER DATABASE [your_db] SET MULTI_USER - 验证是否生效:
SELECT is_read_committed_snapshot_on FROM sys.databases WHERE name = 'your_db'返回 1 即成功
UPDATE 性能提升不明显?先看 tempdb 压力
行版本控制把锁冲突转成了 tempdb 的 I/O 和空间压力。如果 UPDATE 频率高、数据修改量大,tempdb 可能成为新瓶颈:
-
tempdb文件应配置多个等大小的数据文件(建议 4–8 个),避免 PFS/SGAM 争用 - 监控
tempdb空间使用:SELECT * FROM sys.dm_db_file_space_usage - 观察版本存储增长:
SELECT version_store_reserved_page_count FROM sys.dm_db_file_space_usage - 若
version_store_reserved_page_count持续飙升,说明有长时间未提交的事务在拖住旧版本清理
特别注意:即使没显式使用快照隔离,只要启用了 READ_COMMITTED_SNAPSHOT,所有 READ COMMITTED 语句都走版本路径,tempdb 就逃不掉。
SNAPSHOT 隔离 vs READ_COMMITTED_SNAPSHOT:别混用
两者都依赖行版本,但作用范围和触发条件完全不同:
-
READ_COMMITTED_SNAPSHOT是数据库级开关,影响所有READ COMMITTED事务(SQL Server 默认隔离级别) -
ALLOW_SNAPSHOT_ISOLATION开启后,需显式SET TRANSACTION ISOLATION LEVEL SNAPSHOT才生效,提供事务级一致性快照 - 同时开启二者不会冲突,但
SNAPSHOT事务对版本数据的保留要求更高(直到事务结束),更容易撑爆tempdb - 线上高频
UPDATE场景,优先只开READ_COMMITTED_SNAPSHOT,避免额外事务语义复杂度
最易被忽略的一点:行版本控制无法消除写-写冲突。两个事务同时 UPDATE 同一行,后一个仍会被阻塞或报死锁——它只解决读-写冲突。真要降低写冲突,得靠业务层重试、分片、或调整更新粒度。










