结论:SQL Server 存储过程中 SET TRANSACTION ISOLATION LEVEL 必须置于 BEGIN TRANSACTION 之前,MySQL 中则需在 START TRANSACTION 前执行 SET SESSION TRANSACTION ISOLATION LEVEL,否则事务内首条语句仍按旧级别执行,脏读风险未消除。

直接说结论:在 SQL Server 存储过程中,SET TRANSACTION ISOLATION LEVEL READ COMMITTED 必须放在 BEGIN TRANSACTION 之前;MySQL 存储过程里则需用 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,且必须在事务启动前执行——否则隔离级别不生效,脏读照常发生。
SQL Server 存储过程里 SET TRANSACTION ISOLATION LEVEL 放错位置就失效
常见错误是把 SET TRANSACTION ISOLATION LEVEL READ COMMITTED 写在 BEGIN TRANSACTION 后面。这时前几条语句(比如 SELECT)已经按默认的 READ UNCOMMITTED 或旧级别执行了,隔离级别变更对它们无效。
- 正确顺序:先
SET TRANSACTION ISOLATION LEVEL READ COMMITTED,再BEGIN TRANSACTION,最后执行业务逻辑 - 嵌套调用场景下,不能只靠
IF @@TRANCOUNT = 0判断是否设级别——外层事务已开启时,内层存储过程仍需独立设级别,否则继承外层级别(可能仍是默认READ COMMITTED,但若外层显式设过READ UNCOMMITTED就危险) - 数据库级开启
READ_COMMITTED_SNAPSHOT ON后,会话级SET会被忽略,此时实际走的是行版本快照,不是锁机制——这点必须和开发、DBA 对齐,不能假设SET一定生效
MySQL 存储过程中隔离级别设置必须在 START TRANSACTION 前
MySQL 不支持在存储过程里用 SET GLOBAL 动态改全局级别,而 SET SESSION 只影响当前连接后续语句。如果写在 START TRANSACTION 后,事务内第一条 SELECT 仍按旧级别执行。
- 必须在
START TRANSACTION之前执行SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - 若存储过程被其他存储过程调用,且调用方已开启事务,那本过程里的
SET SESSION无效——MySQL 的事务隔离级别是会话级的,一旦事务开始,级别就固定了 - InnoDB 引擎下,
REPEATABLE READ是默认级别,它比READ COMMITTED多防不可重复读,但要注意:同一事务中多次SELECT看到的是事务启动时的 MVCC 快照,而UPDATE/SELECT FOR UPDATE看的是最新已提交数据,这个差异容易引发逻辑 bug
别信注释,也别指望 DBA 统一配置
有人在存储过程开头加注释 -- SET TRANSACTION ISOLATION LEVEL READ COMMITTED,指望 DBA 在部署时全局替换。这在生产环境几乎必然失败:不同环境(测试/预发/生产)配置不一致、应用连接池复用连接导致会话状态残留、ORM 框架自动设级别覆盖手动设置。
- 真正可控的方式,是把
SET语句作为存储过程第一行可执行代码(SQL Server)或明确前置语句(MySQL) - SQL Server 中,如果用了
SNAPSHOT隔离,必须确保数据库已启用ALLOW_SNAPSHOT_ISOLATION ON,否则会报错Snapshot isolation transaction failed... - MySQL 中,
READ COMMITTED下每次SELECT都新建 MVCC 快照,而REPEATABLE READ复用事务首个SELECT的快照——选哪个取决于你是否接受“同一事务内两次读结果不同”,而不是单纯看“能不能防脏读”
最易被忽略的一点:隔离级别只管“读”,不管“写”。即使设了 READ COMMITTED,一个未提交的 UPDATE 仍会阻塞其他事务的 SELECT FOR UPDATE 或相同行的写操作——脏读防住了,但锁等待、死锁、超时问题不会自动消失。得结合具体 SQL 走不走索引、是否命中行锁、有没有间隙锁来判断实际行为。

















