位置:首页 > SQL > SQL 存储过程如何设置事务隔离级别:SQL Server 与 MySQL 的正确顺序和常见误区

SQL 存储过程如何设置事务隔离级别:SQL Server 与 MySQL 的正确顺序和常见误区

时间:2026-08-23  |  作者:火苗实验室  |  阅读:0

目录

  1. SQL Server:隔离级别必须先于事务和数据操作
  2. READ COMMITTED 不是无锁读取,照样可能卡住写入
  3. SERIALIZABLE 为什么常常是死锁放大器
  4. MySQL:必须在 START TRANSACTION 之前设置会话级隔离级别
  5. 跨 SQL Server 和 MySQL 迁移时,最容易错的其实是思维模型

前言

很多人排查存储过程里的脏读、阻塞或死锁时,第一反应是“隔离级别不是已经写了吗”。问题往往不在有没有写,而在写的位置对不对、事务是不是已经开始、当前数据库又按什么规则解释这条 SET。这篇文章分 SQL Server 和 MySQL 两条线来讲,先说明隔离级别什么时候才会真正生效,再看 READ COMMITTEDSERIALIZABLE 在生产环境里最容易带来的误判。读完后,你可以据此检查自己的存储过程是顺序写错了,还是并发模型本身就不适合当前级别。

很多人排查存储过程里的脏读、阻塞或死锁时,第一反应是“隔离级别不是已经写了吗”。问题往往不在有没有写,而在写的位置对不对、事务是不是已经开始、当前数据库又按什么规则解释这条 SET

这篇文章分 SQL Server 和 MySQL 两条线来讲,先说明隔离级别什么时候才会真正生效,再看 READ COMMITTEDSERIALIZABLE 在生产环境里最容易带来的误判。读完后,你可以据此检查自己的存储过程是顺序写错了,还是并发模型本身就不适合当前级别。

SQL Server:隔离级别必须先于事务和数据操作

在 SQL Server 存储过程中,SET TRANSACTION ISOLATION LEVEL 要想生效,前提是它必须出现在任何数据操作语句和 BEGIN TRANSACTION 之前。这里的数据操作不仅包括 UPDATEINSERTDELETE,也包括第一条 SELECT

一旦第一条 SELECT 已经执行,当前执行路径上的隔离级别就按当时的生效值锁定,后面再写 SET 也不会回头修正前面的读取行为。

为什么明明写了 READ COMMITTED,还是会出现脏读

常见现象有两种:一是 SELECT 读到了未提交数据,但代码里明明写了 SET TRANSACTION ISOLATION LEVEL READ COMMITTED;二是并发调用同一个存储过程时,一个请求出现脏读,另一个没有。这通常说明隔离级别没有在稳定、统一的时点生效。

正确顺序应该是:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
-- 业务逻辑

有几个细节尤其容易被忽略:

  • 不要把 SET TRANSACTION ISOLATION LEVEL ... 放到第一条 SELECT 之后,再补救已经来不及。
  • 嵌套调用时,不能假设外层事务已经替你设好级别。哪怕 @@TRANCOUNT > 0,内层存储过程也应显式设置,否则可能继承外层留下的 READ UNCOMMITTED
  • -- SET ... 只是注释,不会执行。生产环境里隔离级别是否真的生效,不能靠“代码看起来像写了”来判断。

READ COMMITTED 不是无锁读取,照样可能卡住写入

不少团队把 READ COMMITTED 当成“既安全又轻量”的默认选项,但在 SQL Server 中,这个理解并不完整。

READ_COMMITTED_SNAPSHOT = OFF(SQL Server 默认)时,READ COMMITTED 下的每条 SELECT 仍会加共享锁(S 锁),直到语句执行完成才释放。所以长时间查询并不会悄无声息地读过去,它可能直接阻塞后续 UPDATE,反过来写操作也会拖住读取。

哪些场景最容易把自己拖进阻塞或 deadlock

典型场景是报表类存储过程对历史表做全表扫描,同时业务线程正在更新同一张表上的热点行。结果就是双方互相等待,最后表现为 deadlock、锁等待飙升,或者接口长时间超时。

这类问题可以优先从下面几个方向检查:

  • 看执行计划里是否出现 Table ScanIndex Scan,如果能优化成 Index Seek,通常就能明显缩短锁持有时间。
  • 避免在事务开头直接 SELECT * 读取一批无关字段,查询越重,共享锁持续时间越长。
  • 如果业务属于读多写少,且可以接受快照一致性,可以考虑启用数据库级 ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON

不过要注意,一旦启用了 READ_COMMITTED_SNAPSHOT ON,会话级 SET TRANSACTION ISOLATION LEVEL READ COMMITTED 的行为就会转向行版本快照,不再是传统的共享锁读取。这里的“READ COMMITTED”名称没变,底层机制已经变了。

SERIALIZABLE 为什么常常是死锁放大器

SERIALIZABLE 看起来像是“最安全”的选择,但在大多数业务系统里,它更像是一个代价极高的并发开关。

READ COMMITTED 与 SERIALIZABLE 在 SQL Server 中的阻塞和死锁风险图
SQL Server 两种常见隔离级别的并发这张图适合放在 SQL Server 风险章节后。

在 SQL Server 中,SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 不只是锁住已经读取到的行,还会锁住可能插入新行的索引间隙。也就是说,即便只是查询 WHERE id = 123,只要索引上存在 id BETWEEN 100 AND 150 的空隙,对应范围也可能被一起锁住。

它会怎么把普通业务放大成批量死锁

典型翻车方式是:订单状态更新存储过程默认用了 SERIALIZABLE,等到批量补单时,20 个线程同时跑起来,结果全卡在 INSERT 上,死锁图里到处都是等待键锁。

  • SERIALIZABLE 不适合作为绝大多数业务场景的默认隔离级别。
  • 它对并发性能影响很大,读操作也会显著提高锁冲突概率。
  • 如果目标是真正的强一致,通常更应该优先评估“应用层重试 + 乐观锁”,而不是直接把数据库锁范围放到最大。

MySQL:必须在 START TRANSACTION 之前设置会话级隔离级别

MySQL 和 SQL Server 最容易混淆的点,是它们对“事务何时开始、隔离级别何时固定”这件事的处理不同。

在 MySQL 存储过程中,事务隔离级别是会话级生效的。一旦执行了 START TRANSACTIONBEGIN,当前事务使用的隔离级别就已经固定下来。此时再执行 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,并不会回头影响这个已经开始的事务。

MySQL 里最常见的误写和隐蔽问题

最常见的误写就是先开事务,再设置隔离级别:

START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@session.transaction_isolation;

这样的顺序下,事务内第一条 SELECT 仍然按照旧级别执行,后面的 SET SESSION 只会影响未来的新事务。

更隐蔽的问题出现在存储过程嵌套调用时:如果当前连接上已经存在事务,那么再执行 SET SESSION 也不会中途改变这个事务的隔离级别。也就是说,被外层已开启事务调用的内层过程,不能指望自己在事务进行中“临时改级别”。

更稳妥的写法应当遵循这个顺序:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
-- 业务逻辑

同时建议一起做这几件事:

  • 每次进入关键逻辑前显式覆盖隔离级别,不要依赖连接池复用后的会话残留状态。
  • SELECT @@session.transaction_isolation 检查当前级别,不要只相信配置文件或部署文档里的“统一设置”。
  • 如果过程可能被已有事务调用,就要把“当前级别无法中途变更”纳入设计,而不是假设过程内部一定能重新设定。

跨 SQL Server 和 MySQL 迁移时,最容易错的其实是思维模型

这类问题难排查,不是因为命令本身复杂,而是因为很多人默认认为“只要写了 SET TRANSACTION ISOLATION LEVEL,后面的事务就会自动按我想的方式运行”。实际情况并不是这样。

SQL Server 与 MySQL 设置事务隔离级别的正确顺序对比图
两类数据库的隔离级别生效顺序用一张顺序对比图看清两类数据库中“先设置还是先开事务”的核心差异。

SQL Server 更强调:隔离级别必须在 BEGIN TRANSACTION 和任何数据操作之前就已生效;MySQL 更强调:必须在 START TRANSACTION 之前设置好会话级隔离级别,一旦事务启动就无法中途修改。

两者共同点只有一个:级别变更只对未来语句或未来事务生效,不能回溯修正已经发生的读取。跨库迁移时,如果还沿用同一套事务思维,脏读、阻塞和死锁几乎是迟早会出现的问题。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多