# 数据源不懂的配置请使用默认值 **Published by:** [OutOfToken](https://paragraph.com/@lua/) **Published on:** 2022-01-10 **URL:** https://paragraph.com/@lua/ZMT4YBuerCeWl8PAoV6Z ## Content TL;NR: 请按照HikariCP官方推荐进行配置数据库连接池 dataSource.cachePrepStmts=true dataSource.prepStmtCacheSize=250 dataSource.prepStmtCacheSqlLimit=2048 dataSource.useServerPrepStmts=true dataSource.useLocalSessionState=true dataSource.rewriteBatchedStatements=true dataSource.cacheResultSetMetadata=true dataSource.cacheServerConfiguration=true dataSource.elideSetAutoCommits=true dataSource.maintainTimeStats=false 背景信息介绍: 公司的项目部门使用我们研发中心的框架进行产品开发,再使用动态数据源进行数据库操作的时候出现两种奇怪的现象: 相同的 sql 语句「一种写法是 navicat 格式化后的语句(字符较多), 一种写法是单行压缩的语句(字符较短)」,两种语句在正确执行SQL语句 + java逻辑代码进行处理的时候出现异常 两个条件下,执行结果是不同的. 正确期望 : 出现异常,两种写法语句对应的整个事务回滚,数据库无新增数据. 错误现象 : 长语句事务不回滚,不符合预期 短语句事务回滚,符合预期 一个mybatis的语句包含多个insert(比如执行三个 insert into tablename values)操作,如果中途的语句出现表名错误、字段名称不存在等原因导致sql执行失败 正确期望 : 出现异常,整个事务回滚,数据库无新增数据. 错误现象 : 事务是不回滚,数据部分插入成功 最小化测试代码请参考: https://github.com/HugePages/troubles 代码时序图: 主体代码时序图 现象复现 : 📍 执行不同长度的 sql 语句,当 sql 长度小于2048,事务正常回滚 发起请求 http://127.0.0.1:8080/demo/insert-rollback?type=1&exception=1 type 为 1 http://127.0.0.1:8080/demo/insert-rollback?type=1&exception=1 查看 Wireshark 对应 mysql 协议的监控如图: sql 语句执行 insert1 语句(不超过配置项2048长度) 此时,代码执行符合预期 : 出现异常, spring 的事务管理正常,执行 rollback 操作,数据库没有新数据插入: 查询数据库结果,数量为0 📍 执行不同长度的 sql 语句,当 sql 太长,导致事务不回滚 发起请求 http://127.0.0.1:8080/demo/insert-rollback?type=0&exception=1 type 为 0 查看Wireshark 对应 mysql 协议的监控如图: 此时的 mysql 协议多了一个请求,Close Statement 通过 debug,事务也是交由 spring 进行管理,也执行了 rollback 操作,但是最终数据还是插入到数据库中: 数据插入成功 结合上述的现象,只能怀疑是因为数据库sql长度导致,排查hikari的配置项,修改 spring.datasource.hikari.data-source-properties.useServerPrepStmts=true spring.datasource.hikari.data-source-properties.prepStmtCacheSize=250 spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=200 修改为 prepStmtCacheSqlLimit=2048,再次重复之前的测试,均进行正常回滚. 测试不同配置导致的结果分支 原因分析 追踪代码发现 rollback 之前, Statement 已经 close. 断点执行到 rollbackNoChecks() 时 ,wireshark监控到已经发送了close请求 因为 serverPrepareStatement 进行缓存,但是超过了 sqllimit 的大小, 就调用了realclose方法,进行 statement 的释放 关键步骤: 发送了 Close() 方法,收到响应,设置 transcationInserver 的 statusFlag 为0 ⚠️ 注意,如果执行的 sql 语句存在异常,这个值会在最后抛异常的时候,获取当前事务在服务端状态 最后判断是否执行 rollback 的时候, 直接 return //no-op,不发送 rollback 指令给 mysql server 补充代码执行大致流程 @2022-0117 参考资料如下: transactions-are-not-getting-rollbacked-with-hikaricp https://stackoverflow.com/questions/50234214/transactions-are-not-getting-rollbacked-with-hikaricp HikariCP 的 MySQL-Configuration https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration 官方 bug 介绍 ( 网页内搜索 「Bug #20212882, Bug #75209」) https://dev.mysql.com/doc/relnotes/connector-j/5.1/en/news-5-1-40.html 官方文档 (5.3.5 Performance Extensions) https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-connp-props-performance-extensions.html sql 监控组件 p6spy https://github.com/p6spy/p6spy Connection 和 Transaction https://docstore.mik.ua/orelly/weblinux2/mysql/ch08_02.htm 扩展阅读: Mysql Connector/J 源码分析(普通 Connection) 如果 MySQL 事务中发生了网络异常? 总结: 公司的开发框架已经发布4年了, 使用次框架开发的产品、项目近百个从来没有遇到过此类问题. 平时的产品项目都会遵守一些分层、数据库使用规范,所以基本很难触发此问题.这次也是项目的开发在设计分层、接口功能拆分、SQL使用等方案不规范导致,没有严格遵守设计原则和开发规范. 此外,框架研发团队在使用开源组件的时候,尽量减少默认参数配置,把参数配置的能力暴露给框架的使用者,特别是所开发的功能作为团队中最基础的公用服务. 🌚 我猜测开发者直接网上在某些博客拷贝了对应的配置信息. ## Publication Information - [OutOfToken](https://paragraph.com/@lua/): Publication homepage - [All Posts](https://paragraph.com/@lua/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@lua): Subscribe to updates - [Twitter](https://twitter.com/HetaoFei): Follow on Twitter