commit 成功为什么数据只更新了部分?

2025-12-12 0 819

前言

昨天在看【java深度调试技术】的时候看到数据库死锁这一章节.然后我就在思考平时写的代码 死锁的场景太多了,为什么工作了七八年从来没遇到因为死锁导致的业务阻塞问题,也从来没有解决过数据库的死锁问题。

今天这篇文章的主题就讲一下为什么我们很少去解决数据死锁问题,围绕这个话题我们会带出一个新的知识点:数据库在某些情况下只会回滚部分SQL 以及 我们通过在什么情况下应该去看日志,什么情况下去查询当前事务的情况。

为什么很少遇到数据库死锁

业务并发低并发低这个是很少发生死锁的主要原因. 除了这个问题之外和数据库本身的死锁机制也有很大的关系.
主流的SQL数据都是有死锁检测机制大同小异罢了。所以当我们的数据库检测到死锁之后,就会回滚部分死锁事务,从不会一直阻塞我们的业务。

MySQL数据库的死锁检测机制

今天就来说一下主流的SQL数据库它的死锁检测机制,当然主要还是讨论一下MySQL就行了。
mysql 的死锁检测机制或者说死锁兜底机制主要有个关键的配置:

  • 第一个是否开启死锁检测配置(默认开启): innodb_deadlock_detect = ON
  • 第二配置等待锁超时时间(默认50s): innodb_lock_wait_timeout = 50

第一步:innodb_deadlock_detect

MySQL的InnoDB引擎通过 等待图(Wait-for Graph)算法来检测死锁。系统会周期性地检查是否存在事务间的循环等待链,一旦发现,就会立即触发死锁处理机制。会回滚一个或者多个死锁事务,选择回滚代价小的事务(插入、更新、删除行数小的)。

测试(mysql8.0)
-----------------------事务1------------------------------
// 第一步:会话 01 ,关闭自动提交事务,更新 navigation
set autocommit = 0;
UPDATE navigation SET name = \'txt01\';

//第三步:更新 navigation_relation_station,一直等待锁
UPDATE navigation_relation_station SET navigation_id = 101;

---------------------事务2--------------------------------
//第二步:会话 02,关闭自动提交事务,更新 navigation_relation_station
set autocommit = 0;
UPDATE navigation_relation_station SET navigation_id = 2;
// 第四步: 更新 navigation ,形成死锁

UPDATE navigation SET name = \'txt02\';

当执行第四步时就会形成死锁,事务1报错回滚,错误信息如下:

UPDATE navigation_relation_station SET navigation_id = 101
1213 - Deadlock found when trying to get lock; try restarting transaction
查询时间: 5.445s

第二步:innodb_lock_wait_timeout

这个配置算是一个等待锁的一个兜底机制,不管是发生死锁(死锁探测机制关闭),还是因为锁被其他事务长时间占用 导致的锁等待超时,这个配置就显得非常重要了。默认50s,根据实际场景调整。 如果超过这个这个配置的设置时间了会发生么?

默认会回滚等待超时事务中获取锁的那条SQL,没错不是回滚整个事务. 当然这只是MySQL的默认策略,这个回滚策略也是可以通过配置控制的。可通过配置(默认关闭,回滚等待锁的SQL)让其回滚整个事务:innodb_rollback_on_timeout = OFF

测试(MySQL8.0)

本次测试当事务在等待锁超时的情况的下,数据库是只回滚部分SQL。并且会话保持,出错之后继续commit,看看能否执行成功,成功的部分是否为事务中未回滚的前半部分语句。

mysql 数据如下,ID为主键索引(避免锁范围扩大)

id realname
1 零零一
2 零零二

做一下测试,模拟等待锁超时的情况:

//第一步,事务1,抢占 ID 为1的行锁,不提交事务
set autocommit = 0;
update t_user set realname = \"事务1\" WHERE id = \"1\";

// 第二步,事务2,先更新ID为2的数据,再更新ID= 1的数据(等待锁)
set autocommit = 0;
update t_user set realname = \"事务2-2\" WHERE id = \"2\";
update t_user  set realname = \"事务2-1\" WHERE id = \"1\";
commit;

第二步的事务等待锁超时,输出结果如下:

update t_user set realname = \"事务2-2\" WHERE id = \"2\"
> Affected rows: 1
> 查询时间: 0s

update t_user  set realname = \"事务2-1\" WHERE id = \"1\"
> 1205 - Lock wait timeout exceeded; try restarting transaction
> 查询时间: 51.353s

再次commit第二的事务,发现能提交成功,刷新列表,发现只有事务中的第一条SQL执行成功了

commit 成功为什么数据只更新了部分?
测试结论:当事务因为等待锁超时(没有开启回滚整个事务),数据会报错,并且事务会回滚等待锁的SQL语句。此时会话依然保持,可以继续commit,事务等待锁之前的SQL将会被重新提交。

如何解决死锁问题

官网和博客好多说使用SHOW ENGINE INNODB STATUS,这个命令能执行成功,但是我从来没有看到过任何信息(不知道怎么回事儿,模拟发生过死锁)。

看死锁日志

日志准备:MySQL8.0 windows 环境中,配置文件需要指定error日志输出文件,以及开启死锁日志记录。

# my.ini 文件配置
[mysqld]
innodb_print_all_deadlocks = 1
log_error = /var/log/mysql/error.log

同时还要确保我们的死锁检测配置时开启的,不然也看不到死锁日志

模拟死锁SQL:

------------------- 事务49421-----------------------
第一步
set autocommit = 0 ;
UPDATE t_user SET realname = \'tx01-1\' WHERE id = \'101\';
第三步
UPDATE t_user SET realname = \'tx02-1\' WHERE id = \'102\';
COMMIT

-----------------事务49422--------------------------
第二步
set autocommit = 0 ;
UPDATE t_user SET realname = \'tx122222-1\' WHERE id = \'102\';
第四步
UPDATE t_user SET realname = \'tx1111-1\' WHERE id = \'101\';
COMMIT;

死锁日志如下:

TRANSACTION 49421, ACTIVE 12 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 8, OS thread handle 31912, query id 130 localhost ::1 root updating
UPDATE t_user SET realname = \'tx02-1\' WHERE id = \'102\'
RECORD LOCKS space id 10 page no 17899 n bits 216 index PRIMARY of table `test`.`t_user` trx id 49421 lock_mode X locks rec but not gap waiting (等待锁 102Record lock, heap no 150 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 3; hex 313032; asc 102;;
 1: len 6; hex 00000000c10e; asc       ;;
 2: len 7; hex 02000001540b3b; asc     T ;;;
 3: len 13; hex 31303231323334333238623363; asc 1021234328b3c;;
 4: len 10; hex 74783132323232322d31; asc tx122222-1;;
 5: len 3; hex 386266; asc 8bf;;

TRANSACTION 49422, ACTIVE 8 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 9, OS thread handle 28276, query id 134 localhost ::1 root updating
UPDATE t_user SET realname = \'tx1111-1\' WHERE id = \'101\'
RECORD LOCKS space id 10 page no 17899 n bits 216 index PRIMARY of table `test`.`t_user` trx id 
49422 lock_mode X locks rec but not gap (持有锁102Record lock, heap no 150 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 3; hex 313032; asc 102;;
 1: len 6; hex 00000000c10e; asc       ;;
 2: len 7; hex 02000001540b3b; asc     T ;;;
 3: len 13; hex 31303231323334333238623363; asc 1021234328b3c;;
 4: len 10; hex 74783132323232322d31; asc tx122222-1;;
 5: len 3; hex 386266; asc 8bf;;

RECORD LOCKS space id 10 page no 17899 n bits 216 index PRIMARY 
of table `test`.`t_user` trx id 49422 lock_mode X locks rec but not gap waiting(等待锁101Record lock, heap no 149 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 3; hex 313031; asc 101;;
 1: len 6; hex 00000000c10d; asc       ;;
 2: len 7; hex 01000001010c84; asc        ;;
 3: len 13; hex 31303131323334333238623363; asc 1011234328b3c;;
 4: len 6; hex 747830312d31; asc tx01-1;;
 5: len 3; hex 386266; asc 8bf;;

不会分析丢给AI就行了。 从上面的信息能看到具体的代码阻塞到的是哪行,这样自己再去定位代码其实也比较简单了。

查询事务表信息

因为我们mysql 自带的死锁检测机制会自动回滚 其中代价小的事务,所以因为死锁导致阻塞的事务就很难通过
查询运行中事务的状态来定位了。

当然如果是系统并发非常高,并且死锁检测机制被关闭,或者说用来查询长事务,可以去通过查看下面的信息来定位问题。

下面这些操作需要数据开启性能模式,默认开启:performance_schema = ON

  • MySQL 8.0 及以上版本:使用 performance_schema.data_locks 和 performance_schema.data_lock_waits 查看锁信息,配合 INNODB_TRX 查看事务详情。
  • MySQL 5.7 及以下版本:仍可使用 INNODB_LOCKSINNODB_LOCK_WAITSINNODB_TRX 这三个表。

推荐使用:performance_schema.INNODB_TRXperformance_schema.data_locks 表就行了;

data_locks:表关键信息事务ID,事务获取锁数据(比如主键值)、等待锁数据,所以这个是很方便定位到死锁的,定位到死锁的事务ID,然后再去INNODB_TRX回查,就能定位到指定的SQL

image.png

INNODB_TRX :表关键信息有事务ID,事务状态,等待执行的SQL。

事务状态字段: trx_state (事务状态,如果在等待锁就会显示:LOCK_WAIT);
事务ID:trx_id;事务等待执行SQL:trx_query ,事务的开始时间等(用来定位挂起的事务就非常方便了)。

image.png

死锁的预防

这个就很简单了,首先就是保持更新表的顺序,比如各业务操作更新多表的时候,可以按照先更新主表再更新副表的顺序,或者直接按照 表名的 字符串排序更新。第二就是减少事务占用的时间 减少锁占用的时间,合理使用索引减少锁的范围,

监控死锁的第三方工具

上面都是基于MySQL 自带日志和事务表来分析的。下面介绍一下一些三方工具吧。
当然现在很多企业都使用大厂的云服务器,基本都自带数据库监控工具。

1. Percona Toolkit(PT 工具集)
  • 作用pt-deadlock-logger专门用于监控和记录 MySQL 死锁信息,可实时抓取死锁日志并输出到文件或数据库,支持定时运行和历史分析。
  • 用法示例pt-deadlock-logger --user=root --password=xxx localhost会持续监控并打印新发生的死锁,也可指定输出到文件:--dest file:/var/log/mysql/deadlocks.log
2. MySQL Workbench
  • 作用:MySQL 官方图形化工具,内置 “Performance” 模块,可直观查看当前事务、锁等待、死锁历史等信息(需连接数据库后在 “Data Export” 或 “Performance Schema” 面板中操作)。
  • 优势:可视化展示锁等待关系,适合新手快速定位问题。
3. Orchestrator
  • 作用:一款开源的 MySQL 高可用管理工具,除了主从切换,还能监控数据库锁状态、死锁事件,并提供告警功能。
  • 适用场景:分布式 MySQL 集群环境,需结合监控平台使用。
4. 监控平台集成(Prometheus + Grafana)
  • 原理:通过 mysqld_exporter 采集 MySQL 锁相关指标(如 innodb_deadlocks 计数器),在 Grafana 中配置仪表盘,实时监控死锁发生频率,并设置告警(如死锁数突增时触发邮件 / 短信告警)。
  • 关键指标innodb_deadlocks(累计死锁次数)、innodb_lock_waits(锁等待次数)等。

MySQL什么场景下数据库会回滚部分SQL

上文说了等待锁超时,默认情况下会回滚等待锁的SQL。除了这种情况还存在其他情况也有这个问题。比如:

官方文档:dev.mysql.com/doc/refman/…

错误类型 InnoDB 的处理方式 简要说明与案例
表空间满  回滚SQL语句 例如,批量插入大量数据时,如果ibdata文件或独立表空间达到上限
事务死锁  回滚整个事务 例如,事务A持有行1锁等待行2,事务B持有行2锁等待行1。InnoDB会选择回滚其中一个事务(让另一个成功)。
锁等待超时  默认回滚当前语句 例如,一条更新语句等待锁超过innodb_lock_wait_timeout设置,该语句被回滚。启用innodb_rollback_on_timeout会回滚整个事务。
重复键错误  回滚单行操作 例如,INSERT多条数据,其中某条违反唯一键约束,仅该行插入失败。
行过长错误  回滚SQL语句 例如,尝试插入一个VARCHAR字段长度超过列定义的数据
其他多数错误  回滚SQL语句 通常由MySQL上层代码检测到,如语法错误等,会回滚对应的SQL语句。

博主测试过 锁等待超时,和 重复键插入,报错之后,继续commit ,报错行之后的SQL不会再被执行,只会提交回滚行之前的SQL。

总结

从为什么很少遇到死锁的原因,到死锁相关的配置,到死锁的解决思路,再到哪些场景数据库在回滚的时候会回滚当前阻塞的SQL。写这篇文章确实比较耗费心力,因为博主也做了很多测试。希望各位兄弟姐妹点个赞 收藏吧。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 commit 成功为什么数据只更新了部分? https://www.zuozi.net/35959.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务