sql server中set transaction isolation level必须置于begin transaction和任何数据操作语句之前,否则隔离级别不生效导致脏读;mysql则需在start transaction前执行set session transaction isolation level,且事务启动后级别即固定不可更改。

SET TRANSACTION ISOLATION LEVEL 必须在 BEGIN TRANSACTION 之前
SQL Server 存储过程中,SET TRANSACTION ISOLATION LEVEL 生效的前提是它出现在任何数据操作语句(SELECT、UPDATE、INSERT、DELETE)和 BEGIN TRANSACTION 之前。一旦第一条 SELECT 执行,隔离级别就按当时已生效的值锁定——后面再 SET 也无效。
常见错误现象:SELECT 读到了未提交的数据,而你明明写了 SET TRANSACTION ISOLATION LEVEL READ COMMITTED;或者两个并发调用中一个出现脏读、另一个没有,说明级别没稳定继承。
- 正确顺序:先
SET TRANSACTION ISOLATION LEVEL READ COMMITTED,再BEGIN TRANSACTION,最后写业务逻辑 - 嵌套调用时,不能依赖外层事务已设好级别——哪怕
@@TRANCOUNT > 0,内层存储过程仍需自己SET,否则可能继承外层曾设过的READ UNCOMMITTED - 别把
-- SET ...当成真执行,注释不会起作用,生产环境没人帮你全局替换
READ COMMITTED 不等于“不加锁”,它会阻塞写操作
在 READ_COMMITTED_SNAPSHOT = OFF(SQL Server 默认)下,READ COMMITTED 隔离级别仍会对每条 SELECT 加共享锁(S 锁),直到该语句执行完成才释放。这意味着长耗时查询会卡住后续 UPDATE,反之亦然。
典型触发场景:报表类存储过程做全表扫描查历史数据,同时业务线程更新同一张表的热点行,结果互相等待,报 deadlock 或长时间超时。
- 检查执行计划是否出现
Table Scan或Index Scan,优先优化为Index Seek - 避免在事务开头就
SELECT *一堆无关字段,锁持有时间直接拉长 - 如果读多写少且能接受快照一致性,考虑启用数据库级
ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON;注意:此时会话级SET会被忽略,实际走的是行版本快照
SERIALIZABLE 容易引发死锁,绝大多数场景不该用
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 在 SQL Server 中不只是锁住查询到的行,还会锁住“可能插入新行的间隙”(Gap Lock)。哪怕你只查 WHERE id = 123,只要索引上有 id BETWEEN 100 AND 150 的空隙,整个范围都可能被锁住。
典型翻车点:订单状态更新存储过程用了 SERIALIZABLE,批量补单时 20 个线程全卡在 INSERT 上,死锁图里全是“等待键锁”。
-
SERIALIZABLE在绝大多数业务场景下都不该作为默认选择 - 它对并发性能影响极大,读操作也会隐式升级为
SELECT ... LOCK IN SHARE MODE级别 - 真要强一致,优先考虑应用层重试 + 乐观锁,而不是靠数据库锁死
MySQL 存储过程里不能用 SET TRANSACTION ISOLATION LEVEL 后再 START TRANSACTION
MySQL 不同于 SQL Server,它的事务隔离级别是会话级生效的,且一旦事务启动(START TRANSACTION 或 BEGIN),级别就固定了。如果把 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED 写在 START TRANSACTION 后面,那事务内第一条 SELECT 仍按旧级别执行。
更隐蔽的问题:若该存储过程被另一个已开启事务的存储过程调用,SET SESSION 就完全无效——因为事务已存在,级别无法中途变更。
- 必须在
START TRANSACTION前执行SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - 不能依赖连接池复用的会话状态,每次进入关键逻辑前都要显式覆盖
- 查看当前级别用
SELECT @@session.transaction_isolation,别信配置文件或部署文档里的“已统一设置”
复杂点在于:SQL Server 和 MySQL 对“事务开始”的定义不同,导致同一套思维在跨库迁移时必然出错;最容易被忽略的是“级别变更只对未来语句生效”,而人总是下意识以为它能回溯修正前面的读操作。











