恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

数据库事务实战:ACID、隔离级别、传播行为与踩坑排障

  • 首页
  • 资讯中心
  • /
  • 数据库事务实战:ACID、隔离级别、传播行为与踩坑排障

相关资讯

Opus5.5逆向工程实战:游戏客户端安全防护的拆解与分析 2026/10/15 5:15:18
OpenClaw大版本解析:Skills重构与MCP标准对齐的迁移指南 2026/10/15 5:15:18
AI产品发布前,如何构建质量门槛与坏消息沟通机制 2026/10/15 5:15:18

最新资讯

Spring Boot跨境电商系统设计:支付回调、汇率锁定与库存防超卖
MCP协议实战:从零构建大模型工具调用标准化方案
TensorFlow实用指南:从模型训练到生产部署的全链路解析
微电网日前经济调度Matlab建模与求解实战指南
直播业务为什么总在晚高峰卡顿?IDC带宽独享与共享的区别
单相正弦波逆变电源设计:DC-DC升压与H桥SPWM全链路解析

今日推荐

AI视频生成的时间一致性难题与时空建模演进
基于bbox的PDF文档解析:多栏重排与水印过滤实战
探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

数据库事务实战:ACID、隔离级别、传播行为与踩坑排障

发布时间:2026/10/15 5:15:18
数据库事务实战:ACID、隔离级别、传播行为与踩坑排障 做后端这些年我见过太多线上事故真查起来十有八九都和“事务”有关。比如对不上账、库存多扣少扣、并发订单产生脏数据根因往往就是事务边界划错了或者隔离级别选得不对。今天这篇把“事务操作”这件事从头到尾捋一遍从ACID四个基本特性到实际写代码时的那些坑再到我平时排查问题用的方法一次性讲透。内容适合写业务代码的开发者特别是刚接触数据库事务、搞不清隔离级别和传播行为怎么选的朋友。1. 事务到底是什么先从转账场景说起1.1 一个经典案例转账为什么会“丢钱”先想象一个最简单的转账场景。账户A里有1000元账户B里有1000元现在A要给B转100元。正常情况下操作完成后A应该是900元B应该是1100元。这个操作在数据库里至少要拆成两条SQL一条是UPDATE account SET balance balance - 100 WHERE id A另一条是UPDATE account SET balance balance 100 WHERE id B。如果这条扣款语句先执行成功入账语句执行失败A的余额变成了900B的余额还是1000那这100元就凭空消失了。更麻烦的是这两条SQL之间如果插入了一条查询你去查A的余额可能正在执行到一半数据的中间状态就被别人看到了。这类问题归根结底是一件事多个数据库操作被拆散执行彼此没有关联任何一个步骤失败都会留下脏数据。事务要解决的就是这个。1.2 事务的定义与核心价值事务简单说就是把一组数据库操作捆绑成一个工作单元这个单元里的操作要么全部成功要么全部回滚不允许出现“做了一半”的状态。数据库层面的实现逻辑大致是这样的事务开始后所有操作都先记录日志操作过程中如果某一步出错数据库根据日志把已执行的操作全部撤销回到事务开始之前的状态如果所有操作都成功才把结果最终落盘。这个机制保证了数据从一个一致的起点走到另一个一致的状态点中间不会出现错乱。可以用一个生活例子来理解你在外卖平台点餐先下单、再付款、然后商家接单。如果付款失败整单取消而不是“下单成功但没付款商家也出餐了”。下单、付款、接单这三个环节被绑定成一个整体事务任何一个环节失败前面已经完成的步骤都要撤销。1.3 哪些业务场景必须考虑事务不是所有代码都需要开事务。单条UPDATE或INSERT本身就有隐式事务执行成功自动提交执行失败自动回滚不需要额外处理。真正需要显式事务的是多个写操作必须作为一个整体提交的场景。最常见的几类订单创建创建订单主表、扣减库存、记录订单快照、生成支付流水这几个动作必须一起成功或一起失败否则可能出现“订单生成了但库存没扣”的情况。账户体系转账、充值、提现、积分变动任何涉及金额加减的操作都必须放在事务里。状态流转审批通过后更新审批单状态、写入操作日志、触发后续流程状态更新和日志写入不能拆开。批量导入导入一批数据时如果中途某条数据格式错误通常要求整批回滚避免出现一半导入成功一半失败。我个人的判断标准很简单有没有两条以上的写操作它们之间有没有业务上的依赖关系如果有就开事务。如果一个接口里只有一次写操作那就不需要画蛇添足。2. ACID特性逐个拆解2.1 原子性全做或全不做的底层逻辑原子性追求的是“执行过程不可分割”。事务中的操作要么全部执行要么全部不执行不存在只做了一部分的中间状态。数据库在实现原子性时核心依赖的是回滚日志。它在事务执行前把要修改数据的原始值记录到日志中事务执行过程中如果某一步出错数据库就会根据日志里的原始值把已经修改的数据恢复原样。这个机制在真实场景里有一个很重要的启示不要在事务代码里随意捕获异常并吞掉。我曾经排查过一个案例业务代码里写了try-catchClassCastException和业务异常都被catch住然后继续往下走事务管理器等不到异常信号不知道需要回滚结果部分数据就真的提交了。这种情况看起来像“原子性失效”其实是代码没把失败信号传递给事务机制。原子性的前提是操作失败必须能被识别并且必须向上传播。如果你在事务里写了catch后不重新抛出等于亲手把数据库的回滚机会掐断了。2.2 一致性业务规则守护者一致性经常被误解。它不是说数据库自己知道你的业务规则而是说事务执行前后数据必须满足所有预设的约束条件。举个例子转账场景里“A和B的余额总和必须保持2000不变”这就是业务约束。数据库没办法自动判断“余额总和该不该变”它只能通过一些硬约束来帮助维护一致性比如字段非空、唯一索引、外键、CHECK约束。但业务上的一致性大部分要靠代码自己保证。这要求开发者在设计事务时想清楚哪些数据关系是不变量哪些状态跳转是合法的比如库存不能为负、订单状态只能从“待支付”变成“已支付”而不能跳成“已完成”、一条订单流水只能对应一次扣款。这里有个很容易忽略的点原子性、隔离性其实都是为一致性服务的。数据库提供了原子性和隔离性这两个工具至于数据最终是否符合业务规则还得看事务里面写的业务逻辑对不对。如果事务里写了一个错误算法把100元扣成200元数据库照样会把这个错误的结果提交。所以谈到ACID一致性是最需要开发者自己把关的一环。2.3 隔离性并发事务如何互不干扰当多个事务同时操作同一批数据时隔离性负责控制它们之间的可见性和相互影响。没有隔离的话事务A还没提交的数据事务B就能读到然后基于这个脏数据继续操作后果不堪设想。数据库实现隔离性的手段主要有两类锁和版本链机制。锁是物理隔离一个事务锁住了某行数据其他事务要修改就得排队等待版本链机制更像一种快照方案每个事务看到的是数据在某个时间点的快照互相之间不直接阻塞。隔离性还引出一个经典权衡隔离得越彻底并发能力越低隔离得越宽松性能越好但要处理各种异常问题。这就涉及隔离级别怎么选了后面第3节细说。这里先记住一个结论隔离性不是越强越好而是要看你的业务到底能不能容忍某些并发读问题。2.4 持久性数据落盘的最后一道防线持久性说的是一个事务一旦提交哪怕系统下一秒宕机重启这次修改也必须永久保留下来。数据库实现持久性靠的是重做日志。事务提交时先把本次修改写入重做日志并刷到磁盘然后才把数据页真正写盘。这样即使数据页还没来得及刷到磁盘就发生了断电数据库重启后也能根据重做日志把修改重新应用一遍。这里要注意一个细节很多新手以为“执行了COMMIT数据就马上写到磁盘了”其实大多数数据库的默认策略是定期批量刷盘并不会每条提交都立刻同步数据文件到磁盘。保证持久性的是重做日志不是数据文件本身。所以不要贸然去关闭日志刷盘策略来提升性能一旦宕机很可能出现已提交事务丢失数据的情况这在金融和订单场景里是不可接受的。3. 隔离级别选择与配置3.1 四种隔离级别怎么选SQL标准定义了四种隔离级别从低到高分别是读未提交、读已提交、可重复读、串行化。隔离级别是否可能脏读是否可能不可重复读是否可能幻读并发能力读未提交会会会最高读已提交不会会会较高可重复读不会不会可能不同数据库处理不同中等串行化不会不会不会最低读未提交基本没人会用它还允许读取另一事务未提交的修改容易读到回滚前的脏数据。读已提交保证只能读到已提交的数据解决了脏读但同一事务内两次读取同一行结果可能不同因为两次读取之间其他事务可能提交了修改。可重复读解决了这个问题在一个事务开始后多次读取同一行数据结果稳定。串行化则是强制事务挨个执行彻底避免所有并发问题代价是性能大幅下降。实际项目里最常见的两个选择是读已提交和可重复读。读已提交适合对一致性要求不是极端苛刻、但并发量很大的系统可重复读则更适合需要在一个事务里多次查询同一批数据保持结果一致的场景。3.2 并发异常问题脏读、不可重复读、幻读这三个问题我用自己的话说一遍比教科书定义好记。脏读事务A修改了一行数据但还没提交事务B读取到了这个未提交的修改。如果事务A最终回滚事务B就等于读了一条根本不存在的记录。不可重复读事务A读取一行数据后事务B提交了对这行数据的修改事务A再次读取时发现同一行的值变了。问题不在于数据错而在于同一事务内前后读到的不一致。幻读事务A按条件查询一批数据查到5条事务B插入了一条满足条件的新记录并提交事务A再次执行同一个查询发现变成6条。多出来的这条就像“幻觉”一样出现。这三种问题对应不同的隔离级别解决方案实践中不要想着一口气解决全部问题。很多业务场景里幻读的影响远没有不可重复读大为了防住幻读把隔离级别提到串行化反而可能把一个正常接口拖到超时。3.3 隔离级别的配置方式配置隔离级别的SQL标准写法是这样的SET TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; -- 业务操作 UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; COMMIT;在框架层面的配置更简单很多情况下直接用注解在方法上指定比如Transactional(isolation Isolation.READ_COMMITTED)。这里要注意一个顺序问题隔离级别必须在事务开始前设置才会对当前事务生效。如果是用框架声明式事务框架会负责在开启事务前把隔离级别设置好但如果你在事务方法内部手动更改隔离级别通常不会生效因为它已经晚了。实操中我更推荐把隔离级别的决策放在全局配置或方法入口统一管理散落在事务中间改级别既不方便排查也容易出线。默认隔离级别通常能满足大多数场景只有当明确出现了不可重复读或幻读问题才需要针对特定方法提升隔离级别。4. 事务实战技巧那些容易踩的坑4.1 事务失效的几种经典场景事务失效是实战中最坑的一类问题代码看着没问题事务就是没生效。第一种是方法自调用。在同一个类中一个方法调用另一个带事务注解的方法直接透传调用并不会经过框架生成的代理对象事务管理逻辑拿不到入口注解形同虚设。解决方式是拆到不同类或者自己注入代理对象再调用。第二种是非public方法。很多框架的事务代理基于动态代理实现private或package-private方法不会被代理拦截。把事务注解写在private方法上基本等于没写。第三种是catch吞掉了异常。事务方法里捕获异常后没有抛出框架不知道执行失败也就不会触发回滚。这不是框架的错是异常处理策略出了问题。第四种是抛出的是检查型异常而框架默认只对运行时异常回滚。如果方法声明了检查型异常比如Exception事务默认不会回滚除非在注解上显式指定回滚规则。第五种是数据库表不支持事务。某些存储引擎本身不支持事务事务注解写再多也没用需要确认表使用的存储引擎支持事务。排查这类问题时我的习惯是先看代理有没有建立再看异常是不是被吞掉然后看异常类型是不是被回滚规则覆盖。按照这个顺序基本都能找到原因。4.2 事务传播行为怎么理解传播行为处理的是“多个事务方法嵌套调用时怎么办”的问题。最常见的有三种REQUIRED是默认传播行为如果当前已经存在事务就加入这个事务如果当前没有事务就新建一个。大多数业务方法适合用这个。REQUIRES_NEW表示无论当前有没有事务都新建一个独立事务。如果已经存在事务当前事务会暂停挂起等新事务提交后再继续。这个适合用在“操作日志记录”上主事务回滚时日志不应该跟着回滚日志要有自己的独立事务。NESTED是嵌套事务当前存在事务时开启一个保存点内层事务回滚时可以只回滚到保存点外层事务仍然可以继续。它和REQUIRES_NEW的区别在于内层回滚不会导致外层事务整体回滚。我举一个实际例子。一个订单接口里做了主业务还调了一个发短信的方法。短信服务不稳定如果短信发送写在主事务里短信服务端超时会导致整个订单事务回滚这是典型的远程调用放进事务里造成的灾难。把发短信的方法传播行为改成REQUIRES_NEW或者干脆移到事务外执行主业务就不会被短信服务拖垮。传播行为不是越多越好用多了事务边界会变得非常难排查。我见到的坏味道是那种层层嵌套的方法每个方法都配了不同的传播行为最终一个请求下来横跨多个数据库连接和多个事务出了问题连日志都不好对齐。能用一种REQUIRED就不要引入REQUIRES_NEW。4.3 大事务拆分与性能优化大事务是线上事故的重要根源之一它的问题不在“事务”本身而在“大”。一个事务执行时间越长锁持有的时间就越长其他事务排队等待的时间也就越长同时这个事务占用的数据库连接也一直没有释放连接池可用连接数会被一点点耗尽更重要的是长时间不提交回滚日志会不断膨胀后续回滚的成本极高还可能拖慢主从同步。导致大事务的常见写法有三种事务里循环几千次更新、事务里调用远程接口、事务里做大量查询计算。大事务拆分的原则很简单把不需要事务保护的操作挪出去。查数据和计算可以放到事务外面事务里只保留最核心的几步写操作需要批量处理时可以分批提交比如每500条提交一次远程调用、发消息、发邮件这类操作一定要挪到事务提交后执行。我自己处理过一个真实场景一个导入功能把上万条数据放在一个事务里跑一次要好几十秒期间相关表的更新全部堵塞。拆分后每次处理500条提交一次整个导入时间明显缩短其他业务也不再被长时间阻塞。事务代码要短平快这个原则从我入行开始就一直强调。一个设计良好的事务方法内部通常不超过十行核心写操作绝不包含等待外部资源的逻辑。4.4 事务与锁的配合事务和锁是伴生关系。事务隔离性依赖锁锁又带来了阻塞和死锁。理解两者的配合才能在设计时少踩坑。两个事务同时修改同一行数据时后到的事务会进入锁等待状态直到先到的事务提交或回滚释放锁。这个等待本身是正常的但如果等待时间过长就会变成锁超时接口直接报错。优化锁竞争有几个通用思路一是让事务执行时间更短尽早提交释放锁二是多个事务并发访问多行数据时固定访问顺序比如都按主键从小到大更新避免互相持有对方需要的锁而形成死锁三是精确命中索引让锁尽量锁在尽量少的行上而不是锁住整张表。关于死锁数据库会主动检测并回滚其中一个事务来解除死锁。但这只是兜底机制业务上还是应该通过设计避开。比如一个统计更新多个账户余额的服务所有线程都按同一顺序更新账户就可以从根上避免绝大部分死锁。5. 常见问题与排查心得5.1 事务没生效先查代理和异常线上发现事务没回滚第一件事不是怀疑数据库而是先看代码。按这个顺序排查第一步确认事务注解是在public方法上。第二步确认这个方法不是从同类内部调用。第三步确认事务方法里的异常没有被catch吞掉。第四步确认抛出的异常类型被回滚规则覆盖。第五步确认数据库表存储引擎支持事务。其中自调用问题最难一眼看出来因为代码上看起来就是普通的方法调用。如果发现同一类里一个方法调用另一个带事务的方法基本可以断定事务失效。拆出独立Service类再调用一次问题立刻解决。还有一个细节事务方法上如果加了final修饰符也可能影响事务代理的生成导致注解不生效。退出设计时尽量保持方法可继承。5.2 死锁排查实录线上出现死锁异常时数据库错误日志一般会有类似“Deadlock found”这样的关键词同时会打印出两个事务各自持有的锁和等待的锁。我处理过一个案例两个服务同时在一个接口里更新订单表和账户表但更新顺序相反一个先更新订单再更新账户另一个先更新账户再更新订单两边互不相让就形成了循环等待。日志里能明显看到两个事务各持有一把锁同时在等对方释放。定位方法分两步。第一步确认死锁涉及哪几条SQL通常在日志里能看到具体SQL文本。第二步看这些SQL对应的索引确认加锁范围是否合理。有些死锁是因为没走索引导致行锁升级成间隙锁甚至表锁加锁范围变大后冲突概率陡然上升。最终的修复通常不是调整死锁参数而是调整代码执行顺序或加锁范围。让所有线程都按同一个顺序访问资源死锁基本就消失了。5.3 数据库连接池被打满留意长事务连接池被打满是个比较常见的线上症状表现是应用大量请求超时新建连接失败。很多人第一反应是调大连接池但实际上很多时候是长事务占着连接不放。常见场景是事务里调用了第三方接口第三方响应很慢事务迟迟不提交数据库连接一直被占用。等第三方恢复后积压的请求全部涌进来每个都新开事务连接池瞬间被打满。排查时先找出当前正在执行且持续时间很长的事务看看它们执行的是什么SQL、由哪个会话发起、已经跑了多久。如果发现一个事务跑了十几分钟还没提交那基本就是问题源头。这类问题从根源上解决只有一条路事务里禁止调用远程接口、禁止等待外部资源、禁止睡。事务方法的重要原则就是“只做最快的数据库操作”。5.4 我常用的排查语句排查时我习惯用几条固定的语句整理出来供大家直接参考。查看当前有哪些事务正在运行可以用系统视图查询比如通过信息架构表查看当前所有未提交的事务关注事务开始时间和已经执行的时间。查看锁等待状态可以用数据库自身的状态报告关注InnoDB一节的锁等待信息。如果出现死锁数据库的状态报告里也能看到最近一次死锁的详细日志包括持有锁和请求锁的事务信息。排查具体事务的耗时在数据库的慢查询日志里找执行时间长的SQL再结合代码定位是哪个方法发起的。定位到方法后重点看这个方法里有没有循环、远程调用或者复杂查询。这几条语句配合起来基本能把长事务、锁等待、死锁三类问题定位到具体SQL和具体代码行。排查事务问题最重要的不是会多少个高级命令而是按照“连接被谁占着、锁被谁拿着、SQL在哪里”这个思路一步步缩小范围。最后分享一个我自己的习惯。每次新建一个涉及多步写操作的接口时我都会在文档里单独写一段“事务边界说明”明确哪里开始事务、哪里提交、哪里回滚、哪些操作放在事务外。这个习惯在排查问题时帮了大忙几次线上问题都是靠这个说明快速圈定了怀疑范围。写代码时顺手理一理事务边界比出问题时熬夜查日志要划算得多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号