欢迎光临
我们一直在努力

Spring Boot 3.2 升到 3.5 后订单落库 P99 翻 6 倍:Hibernat...

Spring Boot 3.2 升到 3.5 后订单落库 P99 翻 6 倍:Hibernate 6.6 主键生成策略迁移排查

问题现象

压测报告是昨天下午四点出的。距离双11还有 5 天,订单写链路在全链路压测里崩了。

单机 8C16G,200 并发,订单落库接口 QPS 从基线的 3200 掉到 890,P99 从 182 ms 涨到 1180 ms。业务日志里刷屏的是同一句:

```Caused by: java.sql.SQLTransactionRollbackException: Lock wait timeout exceeded;try restarting transactionat com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130)at org.hibernate.dialect.MySQLDialect$1.convert(MySQLDialect.java:490)```

关键线索藏在异常上面三行——for update 锁的不是 t_order,而是一张叫 hibernate_sequence 的表。

排查过程

20:10,先怀疑连接池。 版本迁移把 HikariCP 从 5.1.0 带到了 6.2.x,我第一反应是池子参数变了。翻了 /actuator/metrics/hikaricp.connections.active,峰值 37,池上限 50,压根没打满。hikaricp.connections.pending 一直是 0。排除。

21:30,怀疑慢 SQL。 打开 spring.jpa.show-sql 重跑 50 并发,日志里每条 INSERT 前面都跟着一条:

```sqlselect next_val as id_val from hibernate_sequence for updateupdate hibernate_sequence set next_val=? where next_val=?```

一条订单插入,背后三次数据库往返,还带一次 for update。老版本(Spring Boot 3.2.12 + Hibernate 6.4.10.Final)的日志里只有一条 INSERT。这是升级带来的行为差异,不是压测环境问题。

22:40,坐实锁竞争。 SHOW ENGINE INNODB STATUS 里 LATEST DETECTED DEADLOCK 段反复指向同一行:

示意图

```RECORD LOCKS space id 312 page no 4 n bits 72 index PRIMARY of table order_db.hibernate_sequencetrx id 884213 lock_mode X locks rec but not gap```

所有订单线程在抢同一行记录。并发越高,排队越久,innodb_lock_wait_timeout 默认 50 秒都撑不住——说明不是偶发,是结构性问题。

23:20,转折点。 git log 定位到升级 commit,diff 出实体类:

```java@Id@GeneratedValue(strategy = GenerationType.AUTO) // 这一行 4 年没人动过private Long id;```

4 年没动过的注解,在 Hibernate 6.6 里语义变了。

根因分析

Hibernate 6 重写了 GenerationType.AUTO 的解析逻辑:优先按方言能力选择 SEQUENCE,方言不支持序列时回退到 TABLE 生成器。Hibernate 5.6 在 MySQL 上走的是 IDENTITY 分支,6.x 走的是 TABLE 分支。

MySQL 8.0.36 没有原生 sequence,于是 Hibernate 建了 hibernate_sequence 单行表,每次取号都要 select … for update + update。这行的排他锁是全局的,所有订单写入串行化。

这里有个坑要单独说:官方迁移指南写的是「AUTO 会优先使用序列」,听起来是性能优化。但在 MySQL 方言下它退化成表生成器,实际比 IDENTITY 还慢一个数量级。照文档字面理解去改,反而踩得更深。

雪上加霜的是 hibernate.jdbc.batch_size=100 在这个路径下也失效了——表生成器的取号语句夹在 INSERT 之间,JDBC batch 被反复打断。

解决方案

改回 IDENTITY 最省事,但 IDENTITY 天生不支持 JDBC 批量插入(Hibernate 必须逐条拿自增主键)。订单表每秒写入几千行,这个代价不能接受。

最终选的是「显式表生成器 + pooled-lo 优化器」:取号步长放大到 500,锁竞争直接降到 1/500。

```java@Entity@Table(name = "t_order")public class Order {

@Id@GenericGenerator(name = "order_id_gen",strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",parameters = {@Parameter(name = "sequence_name", value = "hibernate_sequence"),@Parameter(name = "initial_value", value = "1"),@Parameter(name = "increment_size", value = "500"),@Parameter(name = "optimizer", value = "pooled-lo")})@GeneratedValue(generator = "order_id_gen")private Long id;

// 其余字段省略}```

配套的 JPA 配置(Spring Boot 3.5.0,application-prod.yml):

```yamlspring:jpa:properties:hibernate:jdbc:batch_size: 100batch_versioned_data: trueorder_inserts: trueorder_updates: truedatasource:url: jdbc:mysql://10.20.3.11:3306/order_db?rewriteBatchedStatements=true&useServerPrepStmts=false&sslMode=DISABLEDhikari:maximum-pool-size: 50```

useServerPrepStmts=false 这行别省。Connector/J 9.1.0 下服务端预编译会阻断 rewriteBatchedStatements 的 SQL 重写,batch 参数照样生效但网络往返没减少,实测差 40%。

验证分两步。先确认取号步长生效:

```sql– 压测 1000 单后执行,next_val 应该只前进了 2~3 次(每次 +500)SELECT * FROM order_db.hibernate_sequence;```

再压一轮,同样 200 并发:QPS 890 → 2810,P99 1180 ms → 213 ms,Lock wait timeout 归零。ID 出现跨 500 的跳号,订单号展示层早就做了雪花兜底,业务侧无感知。

示意图

四种发号方案的取舍,我们内部对比过一轮:

| 方案 | 支持 JDBC batch | 号段冲突风险 | 改造成本 | 实测 QPS(200 并发) ||—|—|—|—|—|| IDENTITY 自增 | 否 | 无 | 低(改一行注解) | 890 || TABLE + pooled-lo | 是 | 无(DB 统一发号) | 中(注解+配置) | 2810 || Snowflake(Hibernate 6 内置) | 是 | 需处理时钟回拨 | 高(要改运维时钟策略) | 3120 || Redis 号段模式 | 是 | Redis 故障时需降级 | 中(多一个依赖) | 2900 |

Snowflake 性能最好,但机房 NTP 抖动时回拨过一次,团队不想在大促前引入这个不确定性。

经验复盘

升级 Spring Boot 3.x 小版本时,GenerationType.AUTO 必须显式化,这条已经进了我们的升级 checklist。

另外两件事值得记一笔:Hibernate 5 时代的 hibernate.id.new_generator_mappings 在 6.x 里已无意义,照旧文档抄配置只会在启动日志里堆一串 Unrecognized configuration key 警告;Connector/J 从 8.0.33 升到 9.1.0 后,sslMode 默认值和 useServerPrepStmts 的交互也变了,URL 参数要重审一遍。

大促前两周不做框架大版本升级,只做补丁位升级——这次算是把这条规矩用一次线上风险换来了。

#后端 #Java #SpringBoot #Hibernate #MySQL


你在实际项目中有遇到类似问题吗?欢迎在评论区分享你的经验和解决方案。

赞(0)
未经允许不得转载:171主机测评 » Spring Boot 3.2 升到 3.5 后订单落库 P99 翻 6 倍:Hibernat...
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址