READ COMMITTED是多数高并发存储过程的合理起点,因其避免脏读、开销低、并发好;SERIALIZABLE因范围锁导致高死锁率而基本不可用,且SQL Server中需用WITH (UPDLOCK, ROWLOCK)替代SELECT FOR UPDATE,并配合事务显式控制锁生命周期。

READ COMMITTED 是多数高并发存储过程的合理起点,不是默认就不用管——它直接影响锁范围、死锁概率和幻读容忍度。
为什么 SERIALIZABLE 在存储过程中基本不能用
显式设 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 后,SQL Server 会对查询涉及的整个范围加范围锁(包括间隙),哪怕只查一行 WHERE id = 123,也可能锁住 id BETWEEN 100 AND 150 的所有空隙。后果是:
- 两个并发执行的存储过程,只要 WHERE 条件落在同一索引范围内,大概率互相阻塞
- 插入新记录时频繁触发
deadlock victim错误,且死锁图里总显示“等待键锁” - 事务执行时间越长,锁住的间隙越多,后续请求排队越严重
典型场景:订单状态更新存储过程里加了 SERIALIZABLE,结果批量补单时 20 个线程全卡在 INSERT 阶段。
READ COMMITTED 下 SELECT FOR UPDATE 不生效?那是你没配对
SQL Server 没有原生 SELECT ... FOR UPDATE 语法,等效写法是 SELECT ... WITH (UPDLOCK, ROWLOCK)。但很多人漏掉关键点:
- 必须搭配
BEGIN TRANSACTION,否则UPDLOCK在语句结束就释放,起不到“先占后改”的作用 -
ROWLOCK提示不强制生效——如果查询走不了索引,优化器仍会升级为页锁或表锁 - 不要在事务开头就
SELECT WITH (UPDLOCK)查一堆无关数据,锁会一直持有着,直到COMMIT
正确姿势:BEGIN TRAN; SELECT id FROM orders WITH (UPDLOCK, ROWLOCK) WHERE order_no = @no; UPDATE orders SET status = 'shipped' WHERE order_no = @no; COMMIT;
READ COMMITTED SNAPSHOT 能否关掉锁等待?看清楚它管什么
启用 ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON 后,普通 SELECT 不再加共享锁,但以下操作依然会加锁:
-
UPDATE/DELETE语句仍需获取排他锁(X锁) -
SELECT ... WITH (UPDLOCK)或WITH (HOLDLOCK)显式锁提示照常生效 - 唯一约束冲突检测(如 INSERT 重复主键)仍需加锁,可能触发
duplicate key错误
它真正解决的是“读-写阻塞”,比如报表查询和库存扣减同时跑,不会因 SELECT 卡住 UPDATE。但它不减少写-写冲突,热点行更新仍要排队。
隔离级别不是调参,是业务契约的落地表达
选错隔离级别,本质是把一致性责任推给数据库,而没想清业务能接受什么:
- 电商下单查库存 → 用
READ COMMITTED+UPDLOCK紧贴 UPDATE 前,允许短暂幻读,但保证不超卖 - 财务对账核总数 → 必须
REPEATABLE READ或快照隔离,宁可慢也不能漏数 - 日志类写入(INSERT only)→ 根本不用改隔离级别,
READ COMMITTED足够,重点在连接池和批量提交
最容易被忽略的一点:同一个数据库里不同存储过程混用隔离级别没问题,但别在同一个事务里动态切换——SQL Server 不支持事务中改 SET TRANSACTION ISOLATION LEVEL,会直接报错。

















