snapshot隔离不能减少update读写冲突,反而使冲突显性化;真正解决读写阻塞的是read_committed_snapshot(rcsi),它在read committed级别下静默启用行版本控制,读写互不阻塞且update不因快照过期失败。

快照隔离(SNAPSHOT)本身不能“减少 UPDATE 读写冲突”——它会让 UPDATE 冲突更显性、更易暴露,而不是掩盖或绕过。真正起作用的是 READ_COMMITTED_SNAPSHOT(RCSI),它在默认隔离级别下静默启用行版本控制,读不阻塞写、写也不阻塞读,且不会引发 UPDATE 冲突回滚。
为什么 SET TRANSACTION ISOLATION LEVEL SNAPSHOT 会导致 UPDATE 失败?
快照隔离是事务级一致性:整个事务从开始就看到一个固定时间点的数据库快照。如果事务 A 用 SNAPSHOT 读取了某行,事务 B 后续修改并提交了该行,事务 A 在尝试 UPDATE 同一行时会直接失败,报错:Snapshot isolation transaction aborted due to update conflict。
- 这是乐观并发的正常行为,不是 bug
- 它要求应用层捕获该错误并重试,否则业务逻辑中断
-
SNAPSHOT隔离下无法通过加锁(如UPDLOCK)提前规避冲突,因为读操作根本不拿锁
READ_COMMITTED_SNAPSHOT ON 才是解决读写阻塞的首选
启用 READ_COMMITTED_SNAPSHOT 后,所有默认的 READ COMMITTED 查询自动使用行版本,但 UPDATE 行为不变——它仍基于最新已提交数据执行,不会因“快照过期”而失败。
- 无需改代码:现有
UPDATE语句照常运行,不报更新冲突 - 读操作(SELECT)不再阻塞写,写操作(UPDATE/INSERT/DELETE)也不再阻塞读
- tempdb 压力比
SNAPSHOT小,因只维护语句级版本,非事务级全量快照 - 支持分布式事务,而
SNAPSHOT不支持
启用 RCSI 的实操要点和常见翻车点
执行以下两步前,务必确认数据库无长事务、无活跃连接:
- 先设
ALLOW_SNAPSHOT_ISOLATION ON(必须,否则 RCSI 无法启用) - 再设
READ_COMMITTED_SNAPSHOT ON(这一步会触发数据库重启版本清理机制,可能卡住几秒到几分钟) - 不要在生产高峰期执行;若卡住,
KILL长事务或等待超时(SQL Server 自动重试) - 启用后,
sys.databases中的is_read_committed_snapshot_on和is_allow_snapshot_isolation_on都应为 1
什么时候才该用 SNAPSHOT 而不是 RCSI?
只有明确需要事务级一致性(比如一个报表查询跨多条 SELECT,必须保证所有结果来自同一时间点),且能接受 UPDATE 可能失败+重试机制时,才考虑显式用 SET TRANSACTION ISOLATION LEVEL SNAPSHOT。
- 典型场景:财务对账、审计导出、ETL 中的源端一致性快照读
- 切勿用于普通 OLTP 的 CRUD 流程,尤其是含 UPDATE 的混合事务
- 注意:
SNAPSHOT事务中若要避免更新冲突,唯一办法是在读时就用UPDLOCK+ROWLOCK提示抢锁,但这又退化回悲观模型,失去快照意义
最常被忽略的一点:RCSI 和 SNAPSHOT 共享同一套行版本存储机制,但 RCSI 的版本生命周期短、压力小;而一旦开了 SNAPSHOT,即使没人用,SQL Server 也会为每个修改事务保留版本直到事务结束——长事务 = tempdb 爆炸风险。所以,开 RCSI 时,ALLOW_SNAPSHOT_ISOLATION 必须开,但 SNAPSHOT 隔离级别本身尽量别用。










