序:在项目工作中需要从三方厂商数据库同步数据到项目业务库中,本平平无奇的功能却被一个报错打破。
在使用某框架的DataSourceConfig(Object)方法初始化数据库连接时,日志输出报错:
Unknown system variable 'transaction_isolation'
经过网上查找相关资料,发现问题现象大同小异都是连接驱动与数据库版本不匹配。从 MySql-5.7.20 版本后 transaction_isolation 作为 tx_isolation 的别名被引入,而在mysql-8.0之后的版本tx_isolation参数被彻底废弃。给出的解决方案无非就是:1.升级数据库的版本。2.降低MySQL连接驱动的版本。
方法1:升级数据库版本
数据库为三方厂商的生产数据库,让他们升级数据库版本似乎不太可能。
方法2:降低MySQL连接驱动版本
要降低连接驱动版本,首先要确定三方厂商的数据库版本,在查询他们的数据库版本时,令人窒息的事情发生了,查询出他们的数据库版本为MySQL-5.7.99。
What?,在MySQL-5.7.20版本后,不是已经有transaction_isolation参数了吗?而且用 show variables like 't%_isolation'; 语句在三方库查询,确实没有transaction_isolation这个系统参数。下表是使用同一语句在三方数据库库和我们数据库查询结果。
表1
三方数据库 | 我方数据库 | |
show variables like 't%_isolation'; | ||
show variables like 't%_read_only'; | ||
select version(); |
可以看到我方查询事务隔离级别的语句是包含transaction_isolation和tx_isolation系统参数,而三方库只有tx_isolation这一个。没办法只能更换驱动包试试,找到MySQL-5.7.20版本适配的驱动包版本是mysql-connector-java-5.1.12,替换系统中的jar包后重启业务应用,结果在启动过程中因为不适配低版本的驱动包导致启动报错。立即更换其他稍微高点版本的驱动,虽然启动没报错了,但是三方的数据库在连接时依然报出Unknown system variable 'transaction_isolation'这个错误。看来更换连接驱动这个办法也行不通。
没办法只能先看看mysql-connector-java驱动的源码,看能不能从源码中找到问题。连接驱动版本为5.1.48。先从报错日志打印的错误堆栈信息入手。
图1
好在方法调用层级不算多,众所周知java方法调用信息使用“栈”结构进行存储,栈弹出逻辑为即“先进后出”,那么最下面的方法就是要排查的起点。从ConnectionImpl.connectWithRetries()方法依次为ConnectionImpl.initializePropsFromServer()、ConnectionImpl.loadServerVariables() ...... 进行查看。
首先看 ConnectionImpl.connectWithRetries()方法,此方法做了些参数初始化后调用了initializePropsFromServer()方法:
图2
在initializePropsFromServer()方法中也是初始化参数后调用了loadServerVariables()方法,进入到loadServerVariables()方法后发现了问题的关键。
图3
其中有行判断数据库版本后,选择在查询系统参数的sql中拼接 @@tx_isolation AS transaction_isolation 还是拼接 @@transaction_isolation AS transaction_isolation语句,而三方库数据库版本为MySQL-5.7.99,不满足条件则拼接的查询语句为 @@transaction_isolation AS transaction_isolation 从而引起报错。查看连接驱动版本大于5.1.48的的源码判断逻辑也是如此,那么就排除是连接驱动jar包bug的问题。可以确定问题就出在了这个不按常理出牌的三方数据库身上。当时正值深夜,而且三方数据库已投入生产使用,也不可能让厂商处理数据库问题,那么就只能看看能不能修改源码规避掉这个问题。
图4 在三方数据库执行查询语句
我方数据库版本也是使用transaction_isolation参数来查询事务隔离级别,但是通过前期的排查,发现同时存在tx_isolation这个系统参数(详见表1)。那么把queryBuf.append(", @@transaction_isolation AS transaction_isolation"); 改为 queryBuf.append(", @@tx_isolation AS transaction_isolation");,不仅可以规避掉三方数据库没有transaction_isolation参数的问题,而且也不会对我们的数据库造成影响。只需要下载源码修改后再编译为jar包就可以了。
从github(https://github.com/mysql/mysql-connector-j/blob/5.1.48/src)上找到5.1.48版本的源码后导入工程,修改完成后发现并不能直接编译,因为还引用了hibernate,而源码中也没找到关于其他依赖引用的描述,依赖问题一时半会也解决不了,那么就直接修改编译后的class字节码文件吧。
把mysql-connector-java-5.1.48.jar中的ConnectionImpl.class文件提取出来,使用jclasslib工具打开,在“方法”中搜索 loadServerVariables,在字节码文件中找到拼接“, @@transaction_isolation AS transaction_isolation”对于的行,点击行中间的常量地址跳转到常量池,在常量池把值修改为“, @@tx_isolation AS transaction_isolation”后保存,把修改后文件放回jar包中。
图5
jar包调整完成后更新应用,发现连接三方库还是有报错,但从这次的异常日志中发现报错的方法变了,原来在getTransactionIsolation()方法中也需要把transaction_isolation改为tx_isolation。还是用上面的方法修改后再更新应用。
图6
再次更新后发现又报Unknown system variable 'transaction_read_only'的错误,看来之前的修改是有效果的。transaction_read_only和transaction_isolation一样,都是在MySQL-5.7.20版本后加进去的,而三方数据库只有tx_read_only这个系统参数,我们的数据库既有transaction_read_only又有tx_read_only,还是可以使用上面的方法进行调整。根据报错调整isReadOnly()方法,调整完成更新后,再次连接三方数据库,可正常连接,同时业务应用没有因为驱动调整而产生报错和异常。
图7
事后在MySQL官网查询,并没有发现又5.7.99的版本,那么友商5.7.99这个版本的数据库到底从何而来?为何数据库会缺少系统参数?至今仍是个迷......
图8
完......