SELECT会读到“看不见”的数据,是因为默认隔离级别下普通查询走“当前读”,未加锁且无快照,导致脏读或不可重复读;需显式开启事务并设置SNAPSHOT等隔离级别实现快照读。

为什么 SELECT 会读到“看不见”的数据?
不是你的 SQL 写错了,而是默认隔离级别下,SELECT 不保证一致性——它可能读到其他事务未提交的中间状态(脏读),也可能在同一次事务里两次 SELECT 返回不同结果(不可重复读)。根本原因在于:普通查询走的是“当前读”,数据库按需拉取最新版本行,没锁、没快照、没锚点。
解决思路很直接:让每次读都基于某个确定的时间点快照,而不是实时拼凑。这需要显式开启事务 + 设置隔离级别,不能靠单条 SELECT 自己搞定。
用 BEGIN TRANSACTION + SNAPSHOT 隔离级别强制快照读
SQL Server 支持真正的快照隔离(Snapshot Isolation),但必须提前开启数据库级开关,且事务内所有 SELECT 才自动走版本存储(tempdb 中的行版本)。
-
先执行一次(仅需一次,数据库级):
ALTER DATABASE [YourDB] SET ALLOW_SNAPSHOT_ISOLATION ON -
每次需要一致读时,显式开启事务并设隔离级别:
BEGIN TRANSACTION; SET TRANSACTION ISOLATION LEVEL SNAPSHOT; - 之后的所有
SELECT(包括不带FROM的SELECT GETDATE())都基于事务启动时刻的快照 - 记得
COMMIT或ROLLBACK,否则快照版本不会清理,tempdb压力会涨
READ COMMITTED SNAPSHOT 是更省心的默认快照方案
如果你不想改每条业务 SQL,只想让所有 READ COMMITTED 查询自动变成快照读(即避免阻塞又避免脏读),就用这个。它不改变应用逻辑,但行为已不同。
- 同样需数据库级启用:
ALTER DATABASE [YourDB] SET READ_COMMITTED_SNAPSHOT ON - 启用后,所有以默认
READ COMMITTED隔离级别运行的SELECT(无论是否在事务中)都自动读快照,不再加共享锁 - 注意:它不影响
REPEATABLE READ或SERIALIZABLE语义,只覆盖默认行为 - 副作用明显:
tempdbI/O 和空间增长更快,尤其在高更新负载下;某些依赖锁等待的应用逻辑可能“突然不卡了”,反而暴露并发问题
MySQL / PostgreSQL 怎么办?没有 SNAPSHOT 关键字
别硬套 SQL Server 语法。MySQL 的 REPEATABLE READ 默认就是 MVCC 快照读(基于事务第一次 SELECT 的快照),PostgreSQL 的 READ COMMITTED 则是语句级快照(每条 SELECT 各自拍快照)。
- MySQL 想全程一致?用
BEGIN; SELECT ...; SELECT ...; COMMIT;,只要不中途COMMIT,两次SELECT就是同一快照 - PostgreSQL 若需跨语句一致,必须用
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ(注意:它不支持幻读防护,但快照固定) - 两者都不支持“数据库级默认开启快照”这种全局开关,必须靠事务包裹或显式隔离级别声明
快照读不是银弹。它把锁竞争转成 tempdb 或 undo log 空间压力,也掩盖了本该被发现的逻辑竞态——比如你依赖两次 SELECT 差值做判断,快照让你“稳定地错”。真正要的不是“读得一致”,而是“读的时机和语义对得上业务意图”。

















