REPEATABLEREAD下聚合查询仍会幻读,因只锁行不锁间隙。SERIALIZABLE需索引支持才能通过范围锁阻止幻读。RCSI可规避幻读但增加tempdb压力且无法防范先查后插逻辑。幻读防护需隔离级别、索引与SQL写法三者配合。
在 REPEATABLE READ 隔离级别下执行 聚合查询,依然可能遭遇 幻读,这个坑不少 SQL Server 开发者都踩过。原因很简单:它只锁住已存在的行,却不锁住行与行之间的间隙。而 SERIALIZABLE 虽然可以通过 RangeS-S 锁阻塞范围插入和删除,但前提是得有 索引支持。RCSI 能规避幻读,但会增加 tempdb 压力,而且对付不了“先查后插”这类逻辑漏洞。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
很多人以为上了 REPEATABLE READ 就万事大吉,其实不然。它确实会锁住已经读取过的行(key lock),但对于那些“还不存在”的行之间的间隙,它睁一只眼闭一只眼。假设你执行 COUNT(*) FROM orders WHERE status = 'pending',另一个事务恰好在此时插入一条新的 pending 记录并提交,第二次查询时结果就会多出一行——这不是脏读,也不是不可重复读,正是 幻读。更常见的是,同一个事务里两次 SELECT COUNT(*) 返回不同数字,比如第一次 12,第二次 13。就算你加了 WITH (HOLDLOCK),如果查询没有走索引,SQL Server 很可能只锁行不锁间隙,该幻读还是幻读。
那有没有能彻底锁死 幻读的办法?有,SERIALIZABLE 隔离级别就是 SQL Server 内置的唯一能真正阻止这类 聚合查询幻读的方案。它会对查询涉及索引范围加 RangeS-S 锁,阻止其他事务在该范围内插入或删除。不过需要注意几点:
BEGIN TRAN 之后、第一个语句之前执行 SET TRANSACTION ISOLATION LEVEL SERIALIZABLEWHERE status = 'pending' 这个字段没有索引,SQL Server 会升级为表锁,并发性能直接崩掉SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;BEGIN TRAN;SELECT COUNT(*) FROM orders WITH (HOLDLOCK) WHERE status = 'pending';-- 后续业务逻辑COMMIT;
如果你能调整数据库配置,不妨考虑 READ_COMMITTED_SNAPSHOT(RCSI)。它比 SERIALIZABLE 轻量得多:开启后,所有 READ COMMITTED 查询会自动从 tempdb 读取行版本快照,天然规避 幻读,而且不加任何锁。启用命令:ALTER DATABASE [YourDB] SET READ_COMMITTED_SNAPSHOT ON。生效后,原先的 READ COMMITTED 行为不变,但底层不再使用共享锁,而是读事务启动时刻的已提交版本。不过要注意,tempdb 的压力会上升,尤其是在高频更新的场景下。另外,RCSI 解决不了业务逻辑上的“先查后插”漏洞——它只保证读一致性,不保证写操作的原子性。
最后说三个容易翻车的硬约束。第一,没有 索引 的 WHERE 条件(比如 status 列无索引),SERIALIZABLE 会降级为表锁,性能直接断崖式下跌。第二,应用层如果分两步走:先 SELECT 判断,再单独 INSERT——这中间的时间窗口,任何隔离级别都救不了。第三,触发器里的 SELECT 不继承外层事务的隔离级别,你也不能在触发器内执行 SET TRANSACTION ISOLATION LEVEL,SQL Server 会直接报错。所以,幻读防护从来不是设个隔离级别就完事,得隔离级别、索引、SQL 写法三者咬合才行。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述