Java多线程并发JDBC事务中,MySQL RR级别下间隙锁导致阻塞是正常现象,核心在于避免被锁:缩短事务、及时提交/回滚、优化SQL与索引、统一插入顺序、运行时精准定位锁冲突。

Java 多线程并发执行 JDBC 事务时,若多个线程尝试向同一索引范围插入数据,极易因 MySQL 的间隙锁(Gap Lock)相互阻塞——这不是线程代码写错了,而是 RR 隔离级别下 InnoDB 的正常行为。关键不在“怎么加锁”,而在“怎么避免被锁住”。
明确事务边界,尽快提交或回滚
间隙锁会一直持有到事务结束。长事务 = 长时间持锁 = 更高冲突概率。
- 用 try-with-resources 确保 Connection、Statement、ResultSet 及时释放
- 显式调用 connection.commit() 或 connection.rollback(),不要依赖连接池自动回收
- 避免在事务内做耗时操作(如 HTTP 调用、文件读写),可拆分为“查→业务处理→再事务写入”
优化 SQL 和索引,减少 Gap 锁触发范围
Gap 锁只在范围查询(>、、<code>BETWEEN、LIKE 'xxx%')且走索引时生效。等值查询(=)+ 唯一索引通常只加记录锁。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- 对高频插入场景的字段(如
order_no、email),建立唯一索引,让冲突走唯一键检查而非 Gap 锁 - 避免
SELECT ... FOR UPDATE扫描大范围;改用精准主键或唯一键查询加锁 - 空表首次
SELECT ... FOR UPDATE会锁整个主键空间(−∞, +∞),务必先插入测试数据再压测
统一插入顺序,规避死锁循环等待
当多个线程并发插入相邻值(如 id=104、105、106),InnoDB 可能为不同事务分配不同间隙锁,进而形成死锁链。
立即学习“Java免费学习笔记(深入)”;
- 插入前对批量数据按主键/唯一键排序,保证各线程加锁顺序一致
- 使用预分配 ID(如雪花算法生成有序 ID),让插入天然具备单调性
- 对同一业务实体(如用户订单),用 user_id 做分片键或作为插入前校验依据,集中写入同一逻辑段
运行时定位与快速干预
出问题时不靠猜,用 MySQL 8.0+ 的 performance_schema 实时诊断。
- 在 Java 异常捕获块中记录被阻塞的 SQL 和参数,便于复现
- 执行
SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TYPE = 'RECORD' AND (LOCK_MODE LIKE '%GAP%' OR LOCK_MODE = 'NEXT-KEY'); - 比对
LOCK_DATA(如100, 110)是否覆盖你正要插入的值(如105),再关联INNODB_TRX找出未提交事务 - 必要时 kill 掉异常长事务:
KILL [trx_mysql_thread_id]

















