记mysql-connector-java:8.0.28的bug排查,你可能也踩坑了

2023-10-30

前言

如标题,最终查明问题是因为 mysql-connector-java:8.0.28 的一个 bug 导致的。但是在真相未浮出之前,整个问题可谓扑朔迷离,博主好久没有排查过如此得劲的 bug ,随着一层层的 debug 深入,真相也随之浮出水面。这个问题属于底层 jdbc 驱动的问题,具有普遍性,可能不知不觉中,你的应用也在线上遭受这个 bug 的摧残,所以,请耐心听我讲完这个故事,然后回去检查下你的应用状态,是否也踩坑了。喜欢直接的可以直接拉到文末结语看结果。

背景

讲故事一般先介绍人物、背景。这里也不列外,先把相关方介绍下。通常,故事情节越丰富越精彩,但是这里博主会考虑篇幅(不讲废话)会把一些与结果走向无关的细节忽略掉,力求叙述完整就好。

  • commons-db : 我们内部维护的,一个采用注解驱动的 Spring 生态下的多数据源管理组件。组件给每个 DataSource 预设了些性能优化的默认值,没有全部列出,不过包含了影响问题走向的属性(useLocalSessionState),如下:
<pre class="prettyprint hljs fsharp">Properties defaultProperties = new Properties();
defaultProperties.put("prepStmtCacheSize", 300);
defaultProperties.put("prepStmtCacheSqlLimit", 2048);
defaultProperties.put("useLocalSessionState", true);
defaultProperties.put("cacheResultSetMetadata", true);
defaultProperties.put("elideSetAutoCommits", true);</pre>
  • java-project : 用来测试组件功能的项目,会作为和出现问题的项目做行为测试对比。spring-boot:2.5.4、mysql-connector-java:8.0.26
  • store:游戏库项目,正是这个项目发现了问题。spring-boot:2.6.6 、mysql-connector-java:8.0.28
  • 阿里云 RDS(MySQL): 阿里云 MySQL 默认的隔离级别为 READ_COMMITTED,而 MySQL 默认的隔离级别为 REPEATABLE_READ

说明: java-project 和 store 的 commons-db 版本其实不一样,因为不影响结果。这里假设他们版本一致。

问题

一天,开发反馈,在 store 项目里使用 commons-db 组件时,出现了事务回滚不生效的问题。如下图代码所示:

<pre class="prettyprint hljs java">@Transactional
@DataSource(type = Type.MASTER,value = "developer")
public void addUser(ApolloUser user){
    userRepository.save(user);
    int i = 1/0; //抛异常
}</pre>
  • 具体表现为:执行 addUser 方法,当 1/0 抛出 RuntimeException 类型异常时,user 对象还是添加成功了。一句话总结就是,【事务回滚不生效了】。

假设

  • 假设1:曾假设过是不是 @Transactional 的 aop 没生效,导致并未开启显式事务。
  • 假设1不成立,因为在开启了 debug 日志模式后,清晰的输出了事务每个阶段的行为日志,如:

  • 假设2:考虑到使用了 commons-db , 如果框架层连接管理问题,导致了事务的开启、事务回滚时获取到的连接不一致,也有可能导致这个问题。
  • 假设2不成立:马上就否了,因为从上面日志上可以看到连接是同一个连接。而且不同连接执行非预期的开启、回滚事务操作应该会抛异常才是。

那么到这里,问题陷入了僵局。不禁沉思,一个看上去人畜无害的代码,一个看上去逻辑清晰的事务日志,为什么会事务回滚失效呢?????

转机

转机1

随后,我在 java-project 项目里,使用相同的 MySQL 测试了下,发现事务回滚成功了。说明这个问题仅仅影响特定的环境,而且可以通过对比两个项目的差异找到问题,离真相更近了。

转机2

开发那边又传来一个关键的信息,在 store 项目中,当设置隔离级别为 REPEATABLE_READ 时,事务回滚生效了。代码如:

<pre class="prettyprint hljs java">@Transactional(isolation = Isolation.REPEATABLE_READ)
   @DataSource(type = Type.MASTER,value = "developer")
   public void addUser(ApolloUser user){
       userRepository.save(user);
       int i = 1/0;
   }</pre>

到这里,然道要怀疑是隔离级别的问题么?显然是不成立的,因为对事务的认知字典里,就没出现过隔离级别影响事务回滚的字条。然后从 java-project 的测试也可以看出,在相同的 RC 隔离级别下,java-project 可以成功。

第一个解决方法

然后终归是向前进了一步了,可以临时用设置隔离级别的办法来解决【事务回滚不生效问题】。不过,不同的隔离级别,对事务锁、并发性能是不一样,这个在调整前必须要有预期。

转机3

事出反常必有妖,本着不信是隔离级别导致的问题,我在 store 项目里将 isolation 设置成 Isolation.READ_UNCOMMITTED ,发现事务回滚也生效了。这也说明了和隔离级别没有直接的关系。然后本着探究【为啥默认的 READ_COMMITTED 导致事务不生效?】的思路排查了下,发现了些问题,如下代码是事务逻辑中的一部分(源码见:DataSourceUtils.prepareConnectionForTransaction()):

 

发现,相比 RR、RU ,差别就是当隔离级别是 READ_COMMITTED 时,不会在对 session 有更新操作了。到这一步也只是多了一个明确的现象,可以解释知道真相后的行为,并没有触达真相边缘。

分析

上文整了一堆,还没发现真实问题。所以先不做其他测试了,先分析下有预期后,在针对性去验证。

先来看下普遍的正常的 Spring Transactional 完整的事务回滚的过程,普遍的指的是没有做过特殊参数配置的,一般这些参数也不会配置。

  • 1、在添加了 @Transactional 的方法执行前,会执行事务管理器(DataSourceTransactionManager)的 doBegin 方法创建一个事务,在 doBegin 方法里,会设置 autoCommit = false。会判当前隔离级别是否和用户定义的一致,否则就更新隔离级别。

  • 2、方法执行失败后,会执行事务管理器(DataSourceTransactionManager)的 doRollback 方法回滚事务。

从 Spring Transactional 的事务日志没看出来问题,创建事务、设置手动提交事务、回滚事务都有日志打印。那么我们就深入到驱动层、或者抓包看,是否这些指令都发到 MySQL Server 了。

定位问题

如分析,在 store 项目中,将断点打在 mysql-connector-java 驱动的 NativeSession.execSQL()方法里,和 MySQL Server 交互的所有指令,最终都会调用这个方法执行。果然发现了问题:

  • 事务回滚失败时, 事务流程并未执行 SET autocommit=0 指令。

等于说事务回滚失败时,事务一直是自动提交的模式,所以,异常回滚操作并不会回滚已经持久化了的数据。

发现这个问题后,接着定位为什么 Spring 执行了 Set autoCommit=false ,而最终确并未执行的问题,这里再次通过【转机1】的 java-project 项目做单步调试对比,发现一段关键代码(ConnectionImpl.setAutoCommit())两个项目里的代码不一致:

java-project,mysql-connector-java:8.0.26(事务回滚生效)

store,mysql-connector-java:8.0.28(事务回滚不生效)

这里稍微介绍下这个参数

  • useLocalSessionState:维护本地 sessionState ,在需要判断 【事务提交模式】、【隔离级别】设置时,获取本地状态,而不是每次像 MySQL Server 发起询问。

这个参数有助于减少和 MySQL 的交互,可以提升写数据性能。所以在参数性能优化时,被默认设置为 true 了。这里,如果 useLocalSessionState=false,则正好会掩盖这个 bug。

解密

因为在 store,mysql-connector-java:8.0.28 有问题的版本的 isAutocommit() 行为逻辑和 isAutoCommit()不一致,本该调用判断 isAutocommit 返回 true 时,却返回了 false。 最终才导致了 store 在接收到 Spring Transactional 设置 autoCommit=false 的请求时,因为 needsSetOnServer=false ,直接跳过了真正的发起 Set autocommit=0 指令的执行。导致当前事务模式是自动提交模式,所以当事务里有任何增删改操作时,会在执行完后立马 commit 持久化。这时如果异常而发起事务 rollback ,自然不会回滚之前已经自动提交的事务。这个很好的解释了开头贴出的事务日志很完整,但是事务就是回滚不生效的问题。

第二个解决方法

排查到这里,第二个解决问题的方法就出现了,只需要让判断是否需要执行 Set autocommit=0 时的 needsSetOnServer=true 成立就行了。所以,只要对 store 应用做如下两个参数任一参数配置调整,则可以解决问题了。这个方法比第一个方法要合适些:

<pre class="hljs ini">useLocalSessionState=false
auto-commit=false</pre>

解释为啥isolation 设置成 Isolation.REPEATABLE_READ 会生效

所以到这里就结束了吗?并没有,预期是即使 useLocalSessionState=ture ,事务也应该完整。然后别忘了 isAutoCommit() 和 isAutocommit() 的差异。先来看下他们的定义:

<pre class="prettyprint hljs kotlin">public boolean isAutocommit() {
  return (this.statusFlags & 2) != 0;
}

public boolean isAutoCommit() {
  return this.autoCommit;
}</pre>

原来在 mysql-connector-java:8.0.28 驱动里,使用 statusFlags 状态代替了 autoCommit 的标识(这里先不考究为什么做这个改动),这个解释了

  • 转机2:当设置隔离级别为 REPEATABLE_READ 时,事务回滚生效了。是因为当用户定义的隔离级别 RR 和默认的 RC 不一致时,会触发 session 设置新的隔离级别,此时也会将 statusFlags = 0 更新为 statusFlags = 2. 故在调用 isAutocommit() 返回 true ,满足了执行 SET autocommit=0 指令的条件。

这里虽然知道了原因,也确切知道 isAutoCommit() != isAutocommit() ,但是为啥做如此改动确并不清楚。这里具体问题暂且不表,先来复现下问题。

复现问题

既然问题已经大差不差的定位到了,那么按常规排查流程,按预期的问题场景复现下,明确下问题边界。因为还还有可能有其他的影响因素一起导致的问题。 在 java-project 项目中,做如下依赖的版本调整

  • 升级 spring-boot:2.6.6 版本和 store 保持一致:问题复现了
  • 保持 spring-boot:2.5.4,调整 mysql-connector-java:8.0.28 :问题也复现了

到这里,基本排除了 Spring Transactional 的嫌疑了。然后将矛头锁定到了 mysql-connector-java:8.0.28 身上。

确认 bug

考虑到从 mysql-connector-java:8.0.26 的 isAutoCommit 更改到了 mysql-connector-java:8.0.28 的 isAutocommit 肯定是有原因的,带着弄清楚代码作者提交这个改动的意图,去翻了下 github。

找了下 github 的提交记录 commit ,发现,最新版本的又改回了 isAutoCommit() 了,然后 Commit Message 明确说明了这是 8.0.28 版本的 bug,如。

至此,终于真相大白了。

修复

最终解决方法

如 8.0.29 release 公告说明,已经修复了 8.0.28 在设置 useLocalSessionState=true 的情况下,autoCommit 状态设置的问题。所以,应用升级到 mysql-connector-java:8.0.29 版本即可

结语

先总结下问题表像为 Spring Transactional 【事务回滚不生效,回滚前提交的数据不会回滚】,根本原因是 【 mysql-connector-java:8.0.28 版本提交的一个改动 bug ,导致在启用 useLocalSessionState=true 的情况下,autoCommit 状态设置有问题】。

然后因为 spring-boot:2.6.3 ~ 2.6.7 ,这五个版本默认的 MySQL 驱动就是 mysql-connector-java:8.0.28 ,而 useLocalSessionState=true 几乎是 Java JDBC DataSource 里的标配,所以这个 bug 估计会影响一大波人。然后因为只是影响回滚操作,所以这个问题会隐藏的很深,不容易察觉,所谓影响深远。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

记mysql-connector-java:8.0.28的bug排查,你可能也踩坑了 的相关文章

  • 我们可以实例化一个抽象类吗?

    在一次采访中 有人问我 我们是否可以实例化一个抽象类 我的回答是 不 我们不能 但是 面试官告诉我 错了 我们可以 我对此争论了一下 然后他告诉我自己在家尝试一下 abstract class my public void mymethod
  • 为什么签名的 Android apk 无法在模拟器上运行

    我已经制作了一个android项目的签名apk 每当我的客户尝试在模拟器上运行它时 他都会遇到以下错误消息 D Android android sdk windows tools gt adb install r abc apk 500 K
  • Windows 8.1 升级后 Apache 无法工作 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 今天从 Windows 8 升级到 Windows 8 1 后 Apache 不再工作 我上次从 Windows 7 升级到 Window
  • 行类型 Spark 数据集的编码器

    我想写一个编码器Row https spark apache org docs 2 0 0 api java index html org apache spark sql Row html输入 DataSet 用于我正在执行的地图操作 本
  • Java 7 中的 Beans Binding 将被什么取代?

    我在某处读到 我忘记了链接 Beans Binding 将不会成为 Java 7 的一部分 有人知道什么会取代它吗 另外 当前版本的 Java 中是否有 Bean 绑定的替代方案 我建议JGoodies 绑定 https binding d
  • MySQL 通过 current_timestamp 选择上个月的数据

    直到今天 当我使用 MySQL 并需要对日期 时间执行操作时 我使用带有 unix 时间戳的 int 列 没有出现任何问题 但今天在阅读了一些指南后 我决定默认使用 current timestamp 测试时间戳列 所以我感兴趣如何按列选择
  • 如何在 JAVA servlet 中处理压缩 (gzip) HTTP 请求(不是响应) - 简单示例?

    我为这个问题苦苦挣扎了很长一段时间 在找到一个简单的解决方案后 想问一个问题和答案 这个问题在堆栈溢出时以不同的方式被多次提出 并且accepted solutions是partially correct and complex或谈论res
  • Java SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") 给出时区作为 IST

    我有 SimpleDateFormat 构造函数作为 SimpleDateFormat yyyy MM dd T HH mm ss Z 我正在解析字符串 2013 09 29T18 46 19Z 我读到这里 Z 代表GMT UTC时区 但是
  • Spring REST 控制器返回带有空数据的 JSON [关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 我有一个简单的 Spring Boot Web 应用程序 我正在尝试从服务器接收一些数据 控制器返回一个集合 但浏览器收
  • 如何修复XSS漏洞

    我们正在使用 fortify 扫描 java 源代码 它抱怨以下错误 Method abc sends unvalidated data to a web browser on line 200 which can result in th
  • Spring WebFlux:在 Spring Data MongoDB 反应存储库中的 null 值时发出异常?

    我正在尝试学习如何使用 MongoDB 反应存储库spring boot 2 0 0 M2 但我担心我没有按预期做事 这是我的方法之一 试图找到一个User通过他们的电子邮件 但如果没有 该方法应该抛出异常 Override public
  • 使用 TestRestTemplate 和 MockRestServiceServer 时,解析异常而不是实体列表不起作用

    我有一个简单的控制器 CODE https github com joergi tryouts blob main kotlin mockrestserver src main kotlin io joergi kotlinmockrest
  • Java - 修剪字节数组中的尾随空格

    我有与此类似的字节数组 77 83 65 80 79 67 32 32 32 32 32 32 32 大致等于 M S A P O C when printed as chars 现在我想修剪尾随空白 使其看起来像 77 83 65 80
  • 多少次函数调用会导致堆栈溢出

    你好 Android Java 开发者 当一个函数调用一个函数并且该函数调用另一个函数等等时 有多少次调用 堆栈长度 会让我陷入堆栈溢出 有一般经验法则吗 我问的原因是因为我现在对于我的 5 人纸牌游戏来说哪个更有效 设计明智 解决方案一
  • 从流中过滤/删除无效的 xml 字符

    首先 我无法更改 xml 的输出 它是由第三方生成的 他们在 xml 中插入无效字符 我得到了 xml 字节流表示形式的 InputStream 除了将流消耗到字符串中并对其进行处理之外 是否有一种更干净的方法来过滤掉有问题的字符 我找到了
  • 注意通知持续时间

    是否可以将抬头通知的持续时间设置为无限 现在它只显示 5 秒 已经尝试过不同的事情 例如更改类别 但持续时间始终为 5 秒 这是我的代码 Notification notification notificationBuilder setCa
  • JavaFX:在 WebView img 标签中未加载本地图像

    以下是我的代码 一切安好 我可以加载远程页面 我可以放置 HTML 内容 但我的img标签显示一个X标志表示无法加载图像 Note 我的图像与类位于同一个包中JavaFX在 Smiley 文件夹中 我可以列出所有图像 这意味着路径没有问题
  • 在Android中创建自定义按钮类

    我正在尝试为我的 Android 应用程序创建自定义按钮类 public class TicTacButton extends Button 我已经在里面设置了所有构造函数TicTacButton并创建了自定义方法和属性 在我的主要活动中
  • javaFX,抛出 NullPointerException,位置是必需的

    我看过其他答案 但没有任何帮助我 抱歉 GUI新手只知道swing的基础知识 这是主课 package application import javafx application Application import javafx fxml
  • JVM 调试端口 7779 正在使用

    我正在使用 RAD 8 当我在调试模式下启动服务器时 它会显示一条错误消息 指出JVM debug port 7779 is in use 我多次遇到这个问题 因为我知道 RAD 使用了这个端口 所以我不得不停止这个过程窗口任务管理器 gt

随机推荐

  • springboot中jsp配置tiles

    tiles是jsp的前端框架 像fream标签一样可以把多个页面组合起来 完成后的目录结构 1 pom xml中添加依赖
  • 深度Linux怎样关闭休眠,deepin如何休眠,

    deepin如何休眠 deepin官网 休眠这个功能还是很酷很实用的 对于Linux系统 休眠一般就是把内存中的数据写入硬盘 swap文件 然后关机 在下一次开机的时候将数据重新载入内存 让你快速回到上一次的工作状态 这在你开启了大量的程序
  • NATAPP使用详细教程(免费隧道内网映射)

    NATAPP https natapp cn tunnel lists NATAPP 在开发时可能会有将自己开发的机器上的应用提供到公网上进行访问 但是并不想通过注册域名 搭建服务器 由此可以使用natapp 内网穿透 购买免费隧道 修改隧
  • 动手学强化学习Day1-基本概念

    文章目录 1 1 什么是强化学习 1 2 强化学习的环境 1 3 强化学习的目标 1 4 强化学习的数据 1 5 强化学习的特征 1 1 什么是强化学习 在机器学习领域 有一类重要的任务和人生选择很相似 即序贯决策 sequential 任
  • C++ win32编程 02 常见消息

    02 常见消息 1 打印消息相关信息 1 1 将消息内容转化为字符串 第一步 定义字符串变量 用来保存转化后的消息 wchar t szInfo 300 定义消息内容变量 第二步 用宽字符格式化函数转化消息内容 wsprintf szInf
  • Ethereum开发

    Ethereum开发 1 简介 1 下载源码 使用Git Bath git clone https github com ethereum go ethereum git 或者使用浏览器下载 2 下载安装包 根据您的系统选择下载 2 官方网
  • Pytorch+LSTM+Encoder+Decoder实现Seq2Seq模型

    usr bin env Python3 coding utf 8 version v1 0 Author Meng Li contact 925762221 qq com FILE torch seq2seq py Time 2022 6
  • sys中stdin与stdout

    如果需要更好的控制输出 而print不能满足需求 sys stdout sys stdin sys stderr就是你需要的 sys stdout与print 在python中调用print时 事实上调用了sys stdout write
  • 特征工程专题

    特征工程 Feature Engineering中文版 特征学习笔记 Fire 特征选择 缺失值处理 数据预处理 林逸飞
  • 嵌入式毕设分享100例(一)

    单片机毕业设计项目分享系列 这里是DD学长 单片机毕业设计及享100例系列的第一篇 目的是分享高质量的毕设作品给大家 包含全面内容 源码 原理图 PCB 实物演示 论文 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的单片机项目缺少
  • ggplot2 画线性回归图

    本介绍如何使用R 可视化库 ggplot2 画拟合的线性回归模型 语法 ggplot data aes x y geom point geom smooth method lm 语法基本格式 其中还包括一些参数后面示例会涉及 1 简单示例
  • 总结:JDK源码

    一 JDK源码下载 地址 http jdk java net java se ri 8 选择版本 下载 RI Source Code 代码路径在 openjdk jdk src windows native 对应着java中的类名 如 jd
  • 200立方发酵罐图纸(总图)【年产600吨青霉素钠盐发酵车间工艺设计】

    200立方发酵罐图纸 发酵 原来指的是轻度发泡或沸腾状态 发酵现象早已被人们所认识 但了解它的本质却是近200年来的事 英语中发酵一词fermentation是从拉丁语fervere派生而来的 原意为 翻腾 它描述酵母作用于果汁或麦芽浸出液
  • VS2022解决方案及项目重命名

    问题 项目写完了才发现当时解决方案及项目的名字起的太狭窄了 就想着换个名字 注意下面的图片 都是已经名字改完的了 因为博客是后写的 名字换完了 产生了两个问题 问题一 文件冲突警告 警告MSB8028 中间目录 Debug 包含从另一个项目
  • 虚拟机无法上网原因合集

    普通原因 1 检查ip地址是否正确 root devin ip a 1 lo
  • 大数据用户画像实战之业务数据调研及ETL

    整个用户画像 UserProfile 项目中 数据 业务及技术流程图如下所示 其中数据源存储在业务系统数据库 MySQL 数据库中 采用SQOOP全量 增量将数据抽取到 HDFS Hive表中 通过转换为HFile文件加载到HBase表 1
  • 主线程中捕获子线程异常

    需求 主线程独立执行 无需等待子线程执行完毕 子线程如有异常抛出可自行catch 网上介绍的方法一般是 1 在线程内部进行try catch捕获异常 2 通过线程池的submit方法 获取Future对象 然后try catch Futur
  • rz 传输错误问题 的几种解决方案

    在使用rz传输文件的时候 会遇到传输错误的问题 如下图 情况1 目录不对 一般情况下 切换到 tmp 目录 先将文件传输到 tmp 下 之后再复制到目标位置 情况2 硬盘空间不足 segmentation fault 使用df h查看磁盘空
  • go语言开发环境的搭建-安装和配置SDK

    一 基本介绍 1 SDK全称 software development kit 软件开发工具包 2 SDK是提供给开发人员使用的 其中包含了对应的开发语言的工具包 运行的工具 开发的工具 以及开发所需要的api 这是把我们的源代码编译成二进
  • 记mysql-connector-java:8.0.28的bug排查,你可能也踩坑了

    前言 如标题 最终查明问题是因为 mysql connector java 8 0 28 的一个 bug 导致的 但是在真相未浮出之前 整个问题可谓扑朔迷离 博主好久没有排查过如此得劲的 bug 随着一层层的 debug 深入 真相也随之浮