前事不忘,后事之师,不忘国耻!

 用户注册  找回密码
 用户注册
搜索
查看: 23|回复: 0

[开发应用] SQL Server 事务与隔离级别详解

[复制链接]

[开发应用] SQL Server 事务与隔离级别详解

[复制链接]
dbaai

主题

0

回帖

51

积分

DBAAI

积分
51
昨天 07:49 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?用户注册

×
一、具体的问题

很多开发把"事务"理解成"多写几条语句、套个事务就完事",结果线上反复出现三种情况:一条 UPDATE 执行到一半报错,前面的数据却没回滚;两个会话同时改同一行,一个干等半天最后报"死锁";报表查询长时间读不到数据,把正常业务也拖住。这三个现象的答案都指向同一个问题:SQL Server 的事务到底是怎么工作的,隔离级别该按什么原则选,死锁又该怎么定位。本文把这三件事一次讲透,并给出可以直接照做的排查步骤。

二、核心原理

先厘清事务的基本行为。SQL Server 里事务由 BEGIN TRAN 开始,COMMIT 提交,ROLLBACK 回滚。默认情况下,每条单独执行的语句自带隐式事务,执行失败自动回滚;只有显式执行了 BEGIN TRAN,收尾的责任才落到你头上——忘记提交、忘记回滚,是最常见的事故来源。

再说隔离级别。隔离级别决定一个事务能"看到"别人改动的程度。SQL Server 提供五种:


  • READ UNCOMMITTED:能读到别人未提交的数据,也就是脏读,适合对准确性要求不高的统计查询。
  • READ COMMITTED:默认级别,读不到未提交数据,但同一个查询在事务内两次执行结果可能不同,即不可重复读。
  • REPEATABLE READ:事务内对已读数据加共享锁、保持到事务结束,解决不可重复读,但可能出现幻读。
  • SERIALIZABLE:级别最高,用范围锁把整个读区间锁住,杜绝幻读,但并发能力最差。
  • SNAPSHOT:靠行版本实现快照读,读不阻塞写、写不阻塞读,是报表类只读场景的好选择。


锁是隔离级别的实现基础。SQL Server 有共享锁和排他锁,读加共享锁、写加排他锁,排他锁之间互斥。死锁的本质是两个会话各自持有资源、又都在等对方释放,SQL Server 会自动选一个会话当牺牲者回滚掉(报错编号 1205),把另一个放行。所以排查死锁的重点不是纠结"为什么会报错",而是找到形成环形等待的那两条语句。

三、实例参考(动手步骤)

下面用一张账户表演示三种典型场景,全部可以在测试库直接执行。

第一步,建表并插入数据:
  1. CREATE TABLE account (
  2.   id INT PRIMARY KEY,
  3.   name VARCHAR(20),
  4.   balance INT
  5. );
  6. INSERT INTO account VALUES (1, 'A', 1000), (2, 'B', 1000);
复制代码

第二步,演示显式事务与隔离级别。开两个查询窗口,窗口 1 执行:
  1. BEGIN TRAN;
  2. UPDATE account SET balance = balance - 100 WHERE id = 1;
  3. SELECT * FROM account WHERE id = 1;   -- 本窗口能看到 900
复制代码

此时窗口 2 执行同一条 SELECT。在默认的 READ COMMITTED 下,看到的是 1000——因为窗口 1 的改动还没提交。窗口 1 执行 ROLLBACK 后,两个窗口都回到 1000。这一步就直观展示了"隔离级别决定可见性、事务决定持久性"。

第三步,演示死锁并定位。窗口 1 先更新 id=1 再更新 id=2;窗口 2 反过来先更新 id=2 再更新 id=1。两个窗口几乎同时执行,其中一个会话会报"事务与另一个进程已被死锁",SQL Server 自动回滚牺牲者。定位方法:执行 sp_who2 看 BLK 列,被阻塞的会话 BLK 列会显示阻塞它的 SPID;再用兼容视图 sys.sysprocesses 查 lastwaittype,出现 LCK_M_X 说明正在等排他锁。更标准的做法是开启跟踪标记 1222,死锁发生后 SQL Server 错误日志会记录完整的死锁图,包含两条语句的文本和锁资源,这是排查死锁最权威的路径。

第四步,前后对比验证。报表只读场景把会话设为 SNAPSHOT 级别(需要在数据库属性里打开 ALLOW_SNAPSHOT_ISOLATION 选项,SSMS 数据库属性页可直接勾选),再让写事务和长报表并行跑。对比可见:读写不再互相阻塞,报表查询稳定拿到提交前一致的数据;但行版本数据会占用 tempdb,写入频繁的库要评估这个代价。改完记得还原会话级别,避免影响其他查询。

四、实操检查清单


  • 显式开了 BEGIN TRAN 后,确认收尾语句是 COMMIT 或 ROLLBACK 二选一,杜绝漏提交。
  • 事务内不放长查询、不做外部服务调用,缩短持锁时间。
  • 报表类只读查询,先评估 SNAPSHOT 或 READ UNCOMMITTED 是否满足准确性要求。
  • 出现 1205 死锁报错时,先抓死锁图分析再改代码,不要靠无限重试或加锁解决。
  • 用 sp_who2 的 BLK 列快速定位阻塞源头,确认阻塞链的起点。
  • 高并发写入的表严格控制事务跨度,避免大事务拖垮日志与行版本管理。
  • 变更前在测试库完整复现场景,验证所选隔离级别符合业务预期。
免责申明1、欢迎访问本站,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@dbabbs.com
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

QQ|Archiver|小黑屋|DBA论坛中国 ( 鲁ICP备20017503号-2 )

GMT+8, 2026-8-22 00:48 , Processed in 0.019115 second(s), 10 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表