Seata + Druid + MySQL8 引发的巨坑

问题描述

最近在项目生产环境部署过程中,发现了一个很奇怪的错误。错误内容如下图所示,大致看下,也就是一条很简单update SQL语句居然无法执行,报空指针异常。该问题从开始发现到解决一共跨了3天,现在总结下。

一开始怀疑是传入给Mybatis的参数某个为null,然后把SQL及参数打印出来看了下,每个参数类型、值都有,数量也是正确的,然后陷入了深思。

深入思考,详细分析,验证猜想

然后冷静下来,将出问题可能性分析如下方面

  1. jar包问题, 生成环境的部署比较特殊
  2. 怀疑和seata有关系,因为此逻辑中存在分布式事务
  3. 和集群部署有关,因为测试环境和本地都是单点
  4. 数据库版本问题

我们按照以上顺序,依次进行验证,1 通过将 测试环境的jar拷贝到生产,很快就验证了,问题一样,所以排除1这种可能性。

验证2,将@GlobalTransaction注解去掉,再次点击业务功能,正常,说明和seata有关系,于是将日志信息重新认真看了一遍,发现之前忽略掉了最后的一段异常信息

于是定位到seata的源代码,发现195行是col对象点了属性,联系上下文,基本可以看出col可能取到为null,然后在前后加上输出,将相关对象内容都序列化输出下,发现果不其然。

好了,接下来就是高难度动作了,要去分析为啥没获取到。于是重新去看了下业务表,发现此字段和其他字段明显有个区别,名字居然是大写。那天晚上当时就下结论了,说是由于seata将字段统一转成小写了,并作为map的key,所以导致获取的时候,拿数据库字段名取获取时获取不到,接下来就想了很多不法,批量刷大写字段,最后还不好弄,同事一个个手动修改的。

第二天一来,项目上再次说还有其他业务有问题,有其他表update时,也出现了一样的问题,并且重启都没用。到这里,有点怀疑昨晚定论可能有点草率了,于是,花了一上午时间,将seata那边的业务调用逻辑用图给画了下。并且怀疑到了最后,可能和数据库的版本有关系了,因为测试环境的版本是MySQL8.0.17 而 项目是 8.0.22,最后通过修改数据库连接,证实了此问题确实和数据库这个小版本差异确实有关。

并且通过进一步分析,其还与druid连接池中的API从ResultSet中获取列名有关系,低版本MySQL,获取到的列名以数据库列大小写为准,而8.0.22版本数据库却以update中的字段名大小写为准。

好了,然后做了两手准备,先让运维的同事将数据库版本降级到8.0.17,同时我这边去看如何对seata的源代码进行优化,让其兼容。而关于SQL语句大小写规范,也只能是强调我们统一使用小写,且数据库全小写,但保不齐有比较有个性的同事存在呢,所以还是想着让代码妥协规范吧。

最终解决方案

修改seata源代码:
seata\rm-datasource\src\main\java\io\seata\rm\datasource\sql\struct\TableMeta.java

    public ColumnMeta getColumnMeta(String colName) {
        return allColumns.get(colName);
        LOGGER.info(">>>>> colName = {}", colName);
        ColumnMeta columnMeta = null;
        columnMeta = allColumns.get(colName);
        if(columnMeta != null) {
            return columnMeta;
        }
        LOGGER.warn("数据库列名{}大小写与SQL语句不匹配,此处对其进行忽略大小写处理", colName);
        Map<String, ColumnMeta> linkedCaseInsensitiveMap=new LinkedCaseInsensitiveMap<>();
        linkedCaseInsensitiveMap.putAll(allColumns);
        columnMeta = linkedCaseInsensitiveMap.get(colName);
        if(columnMeta != null) {
            return columnMeta;
        } else {
            LOGGER.error("from allColumns not find colName = {} ", colName);
            return columnMeta;
        }
    }

由于seata这个模块未引用spring,所以自己扩展一个LinkedCaseInsensitiveMap,这个代码就不用贴了,将spring中的源码拷出来,略加修改即可。

最后我总结下,这个问题影响了我们生产发布工作可能滞后,反思其主要原因:

  1. 对MySQL小版本差异没有足够认识
  2. 分析问题过程有点曲折,没有第一时间定位到问题
  3. 未看源码,提前下结论,导致浪费了不少时间

版本:Seata1.4+Druid1.1.24+MySQL 8.0.22

好了,本期内容就是这么多,希望能够帮助到您,感谢您能读到最后,如果觉得内容不错,请您点赞转发给予鼓励,咱们下期再见!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值