新闻详情

新闻详情

首页 / 资讯中心 / 详情

MySQL事务与分布式事务实战:从ACID到Spring失效与日志排障

发布时间:2026/9/28 14:03:24来源:尧图网络
MySQL事务与分布式事务实战:从ACID到Spring失效与日志排障
“在座各位谁没被线上事务坑过扣款成功但订单没生成、库存扣了却报库存不足、事务日志突然满掉直接拖垮业务——这些都是我工作里真实踩过的坑。”一谈到 MySQL 事务很多刚入门的朋友第一反应是“不就是 BEGIN、COMMIT、ROLLBACK 嘛”但真到线上环境隔离级别选错了会出脏读事务开太大了会把回滚段撑爆Spring 注解一失效整个业务就静默错乱。这篇文章就是基于“mysql 事务”这个核心词把我这些年积累的 MySQL 事务原理、Spring 事务注解的失效场景、分布式环境下订单与库存的一致性问题、以及事务日志满这类经典故障都串起来讲一遍新手能从中理清事务的基础框架有经验的开发也能在某些细节上找到共鸣。适合谁来读只要你写业务代码、管数据库、或者在排查线上数据对不上这种问题这篇文章都值得花十分钟看完。内容不卖弄理论也不会只丢给你一堆命令而是尽量说清楚“为什么这么做”和“踩过的坑长什么样”。1. MySQL 事务基础从 ACID 说起我最早接触 MySQL 事务其实就是被一个转账场景逼着去学的。A 账户扣 1000 块、B 账户加 1000 块这两步操作如果中间哪一步出了错钱就凭空蒸发了。事务就是用来保证这种“要么都成功、要么都失败”的机制这也是它核心的价值。MySQL 里的事务本质上是一组 SQL 操作的集合InnoDB 引擎通过日志、锁、MVCC 这些底层机制把这一组操作打包成一个不可分割的执行单元。1.1 ACID 四个特性到底在防什么我们常说的 ACID是原子性、一致性、隔离性、持久性的缩写。四个特性每个都对应着一类经典问题理解它们能让你少走很多弯路。原子性Atomicity解决的问题是“没做完怎么办”。一个事务包含多条 SQL如果执行到第三条挂了前面两条已经生效的必须要撤销回去InnoDB 靠 undo log回滚日志来实现。每次修改数据之前先把原始数据写到 undo log 里一旦事务回滚就用 undo log 里的旧值把当前值覆盖回来。一致性Consistency是个容易被忽略但很关键的特性。它说的是事务执行前后数据都必须符合业务定义的约束。比如转账场景里A 和 B 的总余额不能变订单场景里下单后库存加锁定的数量要与订单商品数匹配。注意数据库层面的主键、外键、约束能帮一部分忙但真正的一致性逻辑更多要靠业务代码去保证比如“扣库存前先检查库存是否充足”这种事数据库管不了。隔离性Isolation解决的是“并发事务互相干扰”的问题。两个事务同时操作同一行数据如果不做隔离约束可能出现一个事务读到了另一个事务未提交的数据或者两个事务同时改了同一行导致结果错乱。InnoDB 通过锁和多版本并发控制MVCC来实现不同级别的隔离这部分我会在第二章详细展开。持久性Durability是最直观的保证事务提交成功之后数据就不能再丢了。InnoDB 依赖 redo log 来实现这个保证。很多人不理解为什么 InnoDB 要先把日志写进 redo log buffer 再刷到磁盘而不是直接改数据文件。原因是磁盘顺序写 redo log 远比随机写数据文件快得多这种方式叫 WALWrite-Ahead Logging先写日志、再写数据崩溃恢复时靠 redo log 把数据重新补上。1.2 InnoDB 的事务提交与回滚机制InnoDB 在 MySQL 里是默认的存储引擎也是唯一内置事务支持的引擎MyISAM 老早就被淘汰了因为它根本没有事务能力。一个事务从开始到结束要经历这些阶段。先执行 START TRANSACTION 或者 BEGIN 开启事务此时事务进入活动状态。接着执行各种 DML 语句比如 INSERT、UPDATE、DELETE这些操作会直接修改缓冲池Buffer Pool中的数据页同时生成 undo log 记录旧值。在事务还没有提交之前这些修改对外部其他事务是不可见的——具体能不能看见取决于隔离级别。事务提交时InnoDB 会做两件关键的事先把变更写入 redo log buffer之后根据 innodb_flush_log_at_trx_commit 参数的配置来决定什么时候把 redo log 刷到磁盘然后给数据页打上“已提交”的标记并清理事务相关的 undo log有些情况不会立即清理比如有长事务还在读。如果是回滚InnoDB 会从 undo log 里找到修改前的值逆序回放把数据恢复到事务开始前的状态。这里有一个特别容易踩的坑事务里有一条 UPDATE 更新了 100 万行数据中途执行到第 80 万行时发现有脏数据或者断言失败需要回滚。此时回滚的时间可能非常久因为 InnoDB 需要一条一条用 undo log 回放旧值期间表上的锁还不会释放其他查询全部阻塞。我见过一个生产事故就是一条 UPDATE 误操作后执行回滚结果整个库卡了将近二十分钟。所以大事务要拆批一次不要处理太多行这个教训很深刻。1.3 事务的启动与结束方式MySQL 里开启事务主要有三种方式显式START TRANSACTION或BEGIN执行 SET autocommit 0 之后的所有 SQL 都处于一个事务中或者在客户端工具里关闭自动提交后再执行语句。我强烈建议在业务代码中明确使用 START TRANSACTION 和 COMMIT/ROLLBACK而不是依赖 SET autocommit0因为后者很容易出现“事务开着忘了提交”的低级错误尤其连接归还到连接池时如果没清理更会埋下大雷。事务结束只有两种途径提交COMMIT和回滚ROLLBACK另外还有一条隐式提交的野路子需要注意。在 MySQL 中DDL 语句CREATE TABLE、ALTER TABLE 等会隐式提交当前事务在事务执行到一半时你误跑了一条 DDL前面的操作就全被固化下来了。有人写个统计脚本先 UPDATE 数据再顺手 ALTER TABLE 加个索引结果 UPDATE 想回滚已经回滚不了了。这种问题在测试环境兑不出来一上生产就出事千万别让 DDL 混进业务事务里。2. 事务隔离级别与锁并发世界的游戏规则隔离级别是 MySQL 事务面试中被问烂的问题但很多开发者只知道要背一个“可重复读是 InnoDB 默认级别”对于每个级别到底会出现什么问题以及锁和 MVCC 怎么配合脑子里其实是模糊的。这一章我结合具体业务例子来把隔离级别讲透顺带把锁机制和 MVCC 的关系捋清。2.1 四种隔离级别各解决什么问题SQL 标准定义了四个隔离级别读未提交READ UNCOMMITTED、读已提交READ COMMITTED、可重复读REPEATABLE READ、串行化SERIALIZABLE。隔离级别越低并发性能越高但出现的异常现象也越多。读未提交意味着一个事务能读到另一个事务未提交的数据这会产生脏读。比如事务 A 改了订单金额 1000 还没提交事务 B 一查发现变成了 1000拿去做了统计结果 A 回滚了B 的统计就是错的。这个级别在生产环境中几乎不用除非你对一致性毫无要求且对性能要求极变态其实也没快多少因为 InnoDB 在写的时候还是需要加锁。读已提交保证了只有提交后的数据才能被其他事务读到解决了脏读问题。这个级别在 Oracle 和 PostgreSQL 中是默认级别但 InnoDB 的默认级别却是可重复读。读已提交下仍然有一个问题叫不可重复读同一个事务里执行两次相同的 SELECT得到的结果可能不一样。比如你查一笔订单状态是“待支付”然后另一个事务把这笔订单改成“已支付”并提交你在这个事务里再查一次状态就变了。对很多业务来说这种变化是没法接受的因为同一事务内要求看到的数据是一致的。可重复读就是 InnoDB 的默认级别。它解决了不可重复读问题核心原理是在事务第一次执行查询时生成一个一致性快照Read View事务内后续的所有普通查询都基于这个快照因此看到的数据始终一致。可重复读虽然解决了不可重复读但没有解决幻读问题——事务里连续两次执行同样的带范围条件的查询第二次可能多出来一行之前没见过的数据。不过 InnoDB 通过间隙锁Gap Lock和临键锁Next-Key Lock在大部分场景下把这个坑也堵住了尤其在高版本的 MySQL 8.0 中配合默认的可重复读级别幻读实际上很难出现。但如果你的查询条件走的是非唯一索引或者范围条件间隙锁依然会生效此时要注意死锁问题。串行化是最严格的隔离级别所有事务都强制串行执行读写互相阻塞基本可以理解为给表加了大锁。这个级别能解决一切并发问题但并发性能几乎归零生产环境很少使用。如果你的业务对数据一致性要求极高量又不是特别大可以短期考虑但更好的方案应该是通过业务层面设计去规避。不同隔离级别对应的异常问题可以看下面这张表隔离级别脏读不可重复读幻读典型场景READ UNCOMMITTED可能可能可能几乎不用READ COMMITTED不会可能可能报表统计可以凑合REPEATABLE READ不会不会InnoDB 基本防住订单、库存等核心交易SERIALIZABLE不会不会不会极端强一致场景2.2 怎么查看和修改隔离级别MySQL 8.0 中可以这样查看当前的隔离级别SELECT transaction_isolation; SELECT session.transaction_isolation; SELECT global.transaction_isolation;修改隔离级别只需要执行 SET 语句即可-- 设置全局新连接才生效 SET GLOBAL transaction_isolation READ-COMMITTED; -- 设置当前会话 SET SESSION transaction_isolation REPEATABLE-READ;需要注意的是事务隔离级别在事务执行中途不能修改必须在事务开启之前设置才生效。很多人在排查问题时直接执行SET SESSION transaction_isolationREAD-COMMITTED想改变当前事务结果发现根本不起作用就是因为事务已经启动了。配置隔离级别的另一个坑是和连接池有关。你的应用可能用的是 HikariCP 或者 Druid 连接池即使你在数据库端把全局隔离级别改成 READ-COMMITTED连接池在创建新连接时也可以自带初始化 SQL 把会话隔离级别改成它自己配置的值。如果你发现改了数据库全局设置应用的行为却没变请检查连接池配置中是否指定了connectionInitSql或类似参数。我在实际项目中遇到过这种“改了却没生效”的鬼事件最后就是连接池在背后偷偷覆盖了会话设置。2.3 锁与 MVCC 是怎么配合的InnoDB 的锁分为共享锁S Lock和排他锁X Lock。共享锁之间可以兼容多个事务同时持有共享锁没有问题共享锁和排他锁互斥排他锁和排他锁也互斥。普通的 SELECT 不加任何锁走 MVCC 快照读但如果 SELECT 后面带上 FOR UPDATE则加的是排他锁会阻塞其他事务的写操作。UPDATE、DELETE、INSERT 自动加排他锁。MVCC多版本并发控制的核心是 undo log 版本链和 Read View。每一行数据除了业务字段之外还有两个隐藏列trx_id最近修改这行的事务 ID和 roll_pointer指向 undo log 中旧版本数据的指针。当一个事务做快照读时它会基于当前数据库里活跃事务的列表生成一个 Read View然后顺着版本链找到第一个“对当前事务可见”的版本。这就是为什么可重复读级别下你开了事务之后第一次 SELECT 生成快照后面所有 SELECT 都基于这个快照其他事务的提交完全不影响你看到的版本。锁和 MVCC 的关系很容易理解MVCC 帮你解决了读不阻塞写、写不阻塞读的问题而锁负责处理写与写之间的冲突。比如两个事务同时 UPDATE 同一行那么后到的事务必须等前一个事务提交或者回滚释放锁否则会一直等待直到超过innodb_lock_wait_timeout默认 50 秒抛出行锁等待超时错误。关于锁的粒度InnoDB 支持行级锁但行级锁本质上其实是索引记录锁。你需要特别注意如果 UPDATE 语句的 WHERE 条件没有走索引InnoDB 会锁住全表的记录相当于把行锁升级成了表锁性能直接崩掉。“UPDATE 语句为什么把整个表锁住”这类线上问题的排查十有八九是索引缺失导致的。3. Spring 事务实战注解用法与失效避坑Java 后端的环境里Spring 事务基本是标配。但 Transactional 这个注解绝对不是加上就万事大吉了它在很多情况下会默默失效而且失效时没有任何报错业务数据该错还是错排查起来很考验经验。这一章我重点列举几个最常见的失效场景这是我在生产环境踩过、帮别人也排查过的真实问题。3.1 Transactional 的正确打开方式先说基础。Spring 管理事务有两种方式编程式事务基于 TransactionTemplate和声明式事务基于 Transactional 注解。日常开发中 90% 的场景用注解就够了写法很简单Service public class OrderService { Transactional public void createOrder(OrderDTO dto) { orderMapper.insert(dto.getOrder()); stockMapper.deduct(dto.getProductId(), dto.getQuantity()); accountMapper.decrease(dto.getUserId(), dto.getAmount()); } }这个注解只对 public 方法生效private 方法和 protected 方法都不行。这个限制说白了是因为 Spring 的声明式事务默认基于 Spring AOP而 AOP 代理本质上是把外部调用拦截到代理对象上。private 方法没法被子类覆盖所以无法被代理内部调用同一个类里 anotherMethod 调用被 Transactional 标记的方法也不会走代理事务就直接被绕过了。如果你想在一个类内部调用带事务的方法有几种解法拆到另一个类里、往当前类注入自己代理对象、或者用 TransactionTemplate。另外建议大家把事务注解加在 Service 层的接口实现类上而不是加在 Controller 上。Controller 层只是做参数接收和响应返回不该有事务边界。如果 Controller 里开启了事务一个请求里做了大量只需要读的操作也放进事务事务时间被拉长连接占用时间也就变长了高并发下连接池会被占满。3.2 事务失效的五个经典场景第一类是访问权限问题。Transactional 加在 private 方法上不生效这个前面说过。很多人明明知道但随手还是把方法定义成 private 给忽略了。第二类是方法自调用问题。同类中一个普通方法调用了同类中的另一个事务方法因为内部调用不走代理事务不生效。我审查代码时见过最多的就是这种情况一个类写了个 public create() 方法它内部调用 this.createOrder()createOrder 上面虽然加了 Transactional但根本没起作用。第三类是 RuntimeException 回滚策略问题。默认情况下 Spring 只在遇到 RuntimeException 或 Error 时回滚事务受检异常Exception 的直接子类如 IOException不会触发回滚。这个设计是有深意的——受检异常可能表示业务上“可容忍”的情况比如用户余额不足、库存不够这些场景你可能希望事务继续提交或者由你自己决定回滚。如果你希望所有异常都回滚可以在注解上标注rollbackFor Exception.class。我强烈建议大部分业务方法直接写明Transactional(rollbackFor Exception.class)因为你很难预料底层代码会不会抛出一个受检异常而默认不回滚策略很容易让数据处于半完成状态。第四类是数据库引擎问题。如果表是 MyISAM 引擎事务是无效的。现在 MySQL 8.0 默认就是 InnoDB但有些同学会从老项目里迁移数据建表语句里带着 ENGINEMyISAM 没改事务照样不生效。这个问题排查起来很迷惑因为 SQL 执行不报错数据也能写进去就是事务回滚无效。写代码的兄弟一定要注意检查建表语句的引擎类型。第五类是事务被 try-catch 吞掉。这是最常见也是最隐蔽的坑。代码里你在 service 方法内写了 try-catch把异常吃了再打一行日志Spring 感知不到异常发生自然不会回滚。这个场景发生的频率高到什么程度基本上我每次做团队代码评审都会看到几处。处理方案很简单不要在 service 事务方法内部随意 catch 掉异常如果有必须处理的辅助逻辑把异常捕获后重新抛出一个 RuntimeException或者在 catch 块里调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手动标记回滚。我个人更推荐后者因为前者有时候会把自定义错误的提示信息弄丢。3.3 事务传播行为速查Spring 事务传播行为是另一个高频考点。五个常用传播属性建议用一张表总结传播属性含义适用场景REQUIRED默认有事务就加入没有就新建大多数业务方法REQUIRES_NEW挂起当前事务新建一个事务日志记录、审计NESTED保存点嵌套事务分段操作、部分失败可回退到保存点SUPPORTS有就加入没有就算了读操作可走可不走事务NOT_SUPPORTED挂起当前事务不使用事务大查询避免长事务MANDATORY必须存在事务否则报错断言某方法必须被事务内调用NEVER必须无事务否则报错只读操作、测试方法REQUIRES_NEW 和 NESTED 的区别值得重点说。REQUIRES_NEW 是物理上完全独立的新事务内层事务提交或回滚不影响外层事务的状态。NESTED 则利用数据库的 savepoint保存点实现逻辑嵌套内层事务回滚时只回滚到保存点不终止外层事务外层事务后续还可以继续执行和提交。如果内层事务把数据库资源比如锁耗尽外层事务也会受影响所以 REQUIRES_NEW 更彻底更隔离。业务上比如记录操作日志、发消息这种要“即使主流程失败也要记录”的场景用 REQUIRES_NEW 最合适。4. 分布式事务订单与库存的经典难题单机数据库事务再复杂也还是在“一个库”的范围内玩。一旦系统拆成微服务订单服务用的是订单库库存服务用的是库存库一个用户下单的操作要同时扣订单库的余额和库存库的库存这时候单库事务就完全失效了。跨服务的分布式事务是每个做业务架构的人都得面对的硬骨头也是最近面试特别爱追问的热点。这一章我把分布式事务的主流方案和适用场景都梳理一遍。4.1 为什么订单和库存不能一个事务搞定在微服务架构下订单服务和库存服务通常是两个独立的服务各自使用独立的数据库。如果让订单服务直接去操作库存数据库那就要么把接口敞给订单服务、要么共享数据库——这两种做法都违背了微服务“数据独立”的设计原则。所谓分布式事务本质上是要在多个独立的数据库连接、多个独立的事务边界之间达成一个全局一致性的结果。你可能会想能不能用本地消息表能但这里最大的难点是网络是不可靠的。订单服务调用库存服务扣库存如果网络超时订单服务不知道库存到底扣没扣成功库存服务扣成功了但调用方收到超时异常订单服务以为失败回滚了订单库存却已经扣了这就是分布式事务里最典型的“悬挂事务”。再比如库存服务扣库存成功但响应报文在网络上丢了重试时又扣一次导致超扣这也是常见的“重复扣减”问题。4.2 分布式事务的主流方案对比我按实现难度和一致性强度把方案梳理了一下大家可以根据自己项目的实际情况选型。第一两阶段提交2PC。这是最经典也最重的方案。它有一个全局协调者Coordinator先让所有参与者执行 prepare 阶段都准备好了再通知所有参与者执行 commit。两阶段提交能保证强一致性但问题在于协调者单点、prepare 后执行 commit 的过程中参与者宕机协调者无法确认状态只能长时间阻塞整个链路卡死。性能也不行所以生产环境直接用裸 2PC 的很少更多是采用对 2PC 做了改良的方案。第二AT 模式Seata AT。这是阿里 Seata 框架主推的方案。AT 模式本质上是做了增强的 2PC核心思路是在业务 SQL 执行前后自动生成 undo log不是数据库里的 undo log而是 Seata 自己在业务表旁边记录的前后镜像如果事务需要回滚就用 undo log 做补偿。对业务侵入小写起来和本地事务差不多但如果你对性能要求极高、或者表结构非常复杂AT 模式的全局锁和前后镜像生成可能有一定开销。第三TCCTry-Confirm-Cancel。Try 阶段做资源预留比如“冻结库存”Confirm 阶段真正扣减Cancel 阶段释放预留。TCC 对业务侵入比较大每个参与事务的服务都要实现 try、confirm、cancel 三个接口代码量翻倍但性能比 2PC/AT 好且不需要长事务。它适合那种“资源预留”语义很清晰的核心链路比如下单扣库存、余额支付。第四事务消息本地消息表 消息队列。这个是我想重点讲的方案因为它落地相对简单也适合大部分业务场景。流程大概是这样订单服务开启本地事务在业务表里写入订单数据同时往本地消息表写一条消息记录本地事务提交后定时任务或者消息发送组件把本地消息表里的消息投递到消息队列库存服务消费消息执行扣库存如果库存服务执行成功就消费成功并更新消息状态如果失败消息队列会重试同时可以配合反向消息确认来兜底。4.3 事务消息的落地经验事务消息最怕的两件事消息丢失和重复消费。消息丢失主要在本地事务提交后、投递到 MQ 前这个窗口期。你用 RocketMQ 的话可以直接用它的事务消息机制事务消息发出去先处于半消息状态等本地事务提交确认后才允许消费者消费。没有 RocketMQ 这类原生支持时就只能用本地消息表 定时任务扫描发送这种方式更通用但也更容易有延迟。落地时建议给消息表加状态字段和重试次数字段重试达到上限后转人工处理。重复消费这一问题几乎是必然发生的。比如库存服务扣库存成功了但正准备给 MQ 回 ACK 时应用宕机了MQ 会认为消费失败继续重投库存服务又消费了一次导致库存被扣两次。解决重复消费的唯一有效手段是让消费者具备幂等性。最简单也最可靠的方式是建一张消费记录表以消息的唯一 ID 作为主键或者唯一索引消费前先幂等插入插入成功才执行后续业务逻辑。这个幂等表也可以叫去重表配合状态字段防止并发重复。我建议大多数中小团队优先考虑“本地事务 消息队列 幂等消费”的组合它不复杂能覆盖 90% 的异步一致性场景。只有在核心链路的实时性要求特别高、且团队有较强的中间件维护能力时才去引入 Seata 或 TCC 这类重型方案。分布式事务没有银弹很多号称“完美的全局事务方案”背后都是在一致性、可用性和性能之间做取舍。5. 实战经验事务日志满、大事务与排查技巧这一章是纯实战向的干货。我把平时线上最容易遇到的三个事务相关问题集中讲包括事务日志满、大事务危害、以及排查事务问题的一些技巧和工具。每一个都是踩过坑才总结出来的经验。5.1 事务日志已满或过大的经典案例“事务日志已满”这个错误很经典错误文本一般是类似数据库的事务日志已满或者The transaction log for database xxx is full。遇到这种情况第一反应不是去清日志而是要先搞清楚日志为什么满。在 SQL Server 里事务日志满通常是因为事务日志文件达到最大大小限制或者所在的磁盘空间不足而且数据库处于“简单恢复模式”但日志没有被自动截断。但如果你是在 MySQL 环境也会遇到类似问题不过通常表现不是报这个错而是 InnoDB redo log 大小满了导致写不进去。MySQL 的 redo log 是循环写的如果innodb_log_file_size配置得太小而事务非常多或非常大redo log 的写入速度赶不上刷盘速度就会报错或者数据库性能急剧下降。我在一次压测中就遇到这个问题持续跑高并发写流量突然在错误日志里看到了“Log sequence number is in the future”之类的线索一查发现 redo log 太小了。日志满这一类问题处理思路大概三步第一步先判断是磁盘空间不够还是日志文件大小达到上限用df -h看磁盘、用SHOW ENGINE INNODB STATUS或SHOW VARIABLES LIKE innodb_log%看 redo log 相关配置第二步如果是磁盘不足清理过期 binlog 或扩容第三步把innodb_log_file_size调大MySQL 8.0 里可以通过innodb_redo_log_capacity来配置 redo log 容量默认 100MB几百 G 的日志目录很常见。调大 redo log 需要重启实例所以建议一开始就留足余量。5.2 大事务的隐患与避免方法大事务是指运行时间长、涉及数据量大的事务比如一个事务里 UPDATE 了几百万行、或者一个事务里循环调用远程接口导致事务一直不提交。大事务最直接的问题是锁占用时间长。事务里的写操作会一直持有锁直到事务结束期间其他事务的读写全部被堵住。还有一个隐藏问题大事务会导致 undo log 膨胀。InnoDB 的 MVCC 依赖 undo log 保存历史版本事务不提交undo log 不能清理久之会撑爆 undo tablespace甚至影响数据库启动。我曾经排查过一个案例一个定时清理任务把清理 SQL 包在大事务里一次事务删了 500 万条过期数据结果数据库主库从库延迟从几秒飙到十几分钟业务接口大批量超时。最终方案是把删除 SQL 改成按主键分批删除每批 1000 条每批单独提交一次配合 sleep 一百毫秒再执行下一批。这样事务时间缩短到毫秒级对线上基本无感知。避免大事务的心得第一事务里不要做远程调用、不要做耗时的外部 I/O第二大批量操作一定要分批执行分批次提交第三明确控制事务的时间窗口比如对自动任务加上执行时间的上限监控第四核心查询尽量走索引避免全表扫描把锁范围扩大。5.3 事务排查常用 SQL 与工具当你怀疑线上有长事务或锁等待时用下面几个 SQL 能很快定位问题。先看当前所有正在运行的事务SELECT * FROM information_schema.INNODB_TRX\G这条语句会给出事务 ID、事务状态、执行的具体 SQL 语句、锁等待时间等等。如果你想看哪些事务在等待锁、阻塞者是谁直接看这张表就能定位到。查看当前锁的情况用SELECT * FROM performance_schema.data_lock_waits; SELECT * FROM performance_schema.data_locks;这两张表能告诉你锁的持有者和等待者以及锁的类型和锁的对象。配合上面的事务表可以快速判断死锁或者锁等待的根因。对于死锁MySQL 默认会在死锁发生时自动回滚代价较小的事务并把死锁信息记录到错误日志中。你可以直接看错误日志里面会有类似LATEST DETECTED DEADLOCK的段落。分析死锁时要关注的四个要素事务 1 持有哪些锁、等待哪些锁事务 2 持有哪些锁、等待哪些锁以及它们的加锁顺序差异在哪里。如果你们用的云数据库或者自建的 MySQL 版本比较新还可以直接查询 performance_schema 库下的events_statements_current来获取当前正在执行的语句再结合sys.innodb_lock_waits视图一条 SQL 就能看到锁等待链条SELECT * FROM sys.innodb_lock_waits\G注意这些查询本身也会消耗一点数据库资源但在问题排查时完全可以接受。平时建议在监控系统里对information_schema.INNODB_TRX做定期的数据采集这样即使出问题也可以在事后回溯事务的起止时间不用临时摸瞎。另外推荐一个小技巧在测试环境复现锁问题时把事务的隔离级别统一成生产环境的配置否则某些低级别下本来不阻塞的问题生产却因为高隔离级别出现大量锁等待这会让排查方向跑偏。我在实际排查事务问题时体会到的最深的一点是绝大多数的线上问题都不是某一项配置不够好而是多个因素叠加导致的。比如 redo log 配小了 大事务批量更新 连接池不够用单看任何一个都不致命但串起来就形成雪崩。处理时要有全局视角先止血回滚掉长事务、清理阻塞节点再根治调整 SQL、分批执行、调大 redo log 容量最后做复盘沉淀到团队的开发规范里。最后分享一个小技巧如果你在排查 Spring 事务注解是否真的生效可以临时打开 Spring 的 debug 日志关注TransactionInterceptor的输出它会显示每个方法的事务提交和回滚情况。如果发现方法根本没出现在日志里恭喜你那大概率就是自调用或者非 public 方法导致的事务失效对着上面那一节逐个排查就行了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

局域网离线跑通VibeCoding:Claude Code与Codex内网接入全指南 2026/9/28 15:54:46

局域网离线跑通VibeCoding:Claude Code与Codex内网接入全指南

最近聊开发工具,无论如何绕不开 VibeCoding、Claude Code 和 Codex 这三张牌:用自然语言把需求说清楚,剩下的是 AI 生成代码、你负责 review 和取舍。但我的关注点不在"它有多神奇",而在一个偏冷门却非常刚需的玩法——…

阅读更多 →
多元时间序列预测完整工程实战:DSTLinear源码解析与训练调优指南 2026/9/28 15:54:46

多元时间序列预测完整工程实战:DSTLinear源码解析与训练调优指南

简介:多元时间序列预测Python大作业项目源码,围绕时间序列预测核心任务展开,面向高校计算机、数据科学等专业需要完成课程设计或毕业设计的学生。项目覆盖ETT、天气、电力、交通、汇率等常见基准数据集,包含数据处理、模型定义、训…

阅读更多 →
WorkBuddy:本地化桌面工作代理实战入门 2026/9/28 15:54:46

WorkBuddy:本地化桌面工作代理实战入门

1. WorkBuddy不是“另一个AI工具”,而是你桌面上的协作型工作代理WorkBuddy这个词最近在开发者、自学编程者和转行新人的圈子里反复出现,但很多人第一次听到时下意识反应是:“又一个Copilot?还是个ChatGPT插件?”——这…

阅读更多 →
基于Hadoop的分布式云存储系统:从环境搭建到HDFS Java API实践 2026/9/28 15:54:46

基于Hadoop的分布式云存储系统:从环境搭建到HDFS Java API实践

简介:基于Hadoop的分布式云存储系统项目资料包,面向大数据初学者与需要搭建分布式存储实践的开发者。围绕HDFS与MapReduce两大核心组件,演示如何在多节点集群上实现高可靠、高吞吐的数据存储与管理,可帮助读者理解Hadoop基础架构以…

阅读更多 →
KEIL MDK下C文件编译为Lib库的完整指南与实践 2026/9/28 15:54:45

KEIL MDK下C文件编译为Lib库的完整指南与实践

1. 为什么要把C文件编译成Lib库做嵌入式开发时间长了,你会发现一个现象:很多团队在项目交付或者模块复用的时候,不是直接把一堆.c文件丢给对方,而是给一个.lib文件加几个.h头文件。这背后是有实际考量的,不是故弄玄虚。…

阅读更多 →
西门子S7通信TSAP配置详解:从默认值到手动修改 2026/9/28 15:54:33

西门子S7通信TSAP配置详解:从默认值到手动修改

搞自动化通信的老伙计们对TSAP应该不陌生,这词儿听着简单,但它坑起人来是真不含糊。我最早被TSAP折腾,是在一套S7-300的旧产线上,PLC程序下载好、IP地址也能Ping通,可上位机就是怎么都连接不上,连西门子自己…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉