synchronized套数据库操作会锁死,是因为Java线程在持有synchronized锁的同时长时间占用未提交的数据库连接,导致其他线程既无法获取Java锁,也无法从连接池获取新连接,形成Java层与数据库层的双重阻塞。
为什么synchronized套数据库操作会锁死
不是数据库本身卡住,而是java线程在持有java层锁的同时,长时间占用数据库连接并没提交,导致其他线程既拿不到java锁、也抢不到连接——形成双重阻塞。典型场景是:一个synchronized块里执行了select ... for update但没commit或rollback,后续线程在等待锁时,连接池又因超时或耗尽无法分配新连接。
OracleDataSource和连接池必须禁用自动提交
Oracle JDBC驱动默认开启auto-commit,但XA或显式锁场景下必须关掉,否则FOR UPDATE语句一执行就自动提交,锁瞬间释放,起不到同步效果;而开着它又容易让事务边界失控。关键配置项:
-
HikariCP中设auto-commit=false,且不要依赖@Transactional的默认行为去控制 -
OracleDataSource创建后,显式调用setConnectionProperties注入user、password和autoCommit=false - Spring Boot中若用
spring.datasource.hikari.auto-commit=false,需确认底层驱动实际生效(某些旧版驱动会忽略该参数)
多线程锁顺序与SQL执行顺序必须严格一致
Java层synchronized对象顺序和SQL访问表/行的顺序不一致,是引发数据库+应用层联合死锁的主因。比如线程A先锁objA再更新account_1然后account_2,线程B却先锁objB再更新account_2再account_1——即使Java锁不冲突,Oracle行锁也会交叉等待。
- 所有涉及多资源更新的路径,统一按主键升序、ID字典序或业务约定顺序访问记录
- 避免在
synchronized块内动态拼接SQL或根据条件跳过某张表——这会导致锁序不可预测 - 用
SELECT ... FOR UPDATE ORDER BY id强制行锁获取顺序,比单纯WHERE id IN (x,y)更可控
别指望setTransactionTimeout()能救活已挂起的连接
这个方法只影响JTA事务管理器倒计时,对底层JDBC连接无感知。如果线程卡在synchronized里、连接被占着不放,超时根本触发不了回滚,V$GLOBAL_TRANSACTION里照样显示STATE = 'ACTIVE'。
- 真正有效的是连接池层的
connection-timeout=30000和leak-detection-threshold=60000,逼迫连接尽早归还或报错 - JDBC URL加
oracle.net.READ_TIMEOUT=6000,防止网络卡顿让SQL无限挂起 - 业务代码里别写
Thread.sleep()或长循环在synchronized块里——这是人为制造“锁持有时间不可控”
ORA-00060也不抛DeadlockLoserDataAccessException,只表现为接口响应慢、连接池耗尽、CPU空转——得同时查V$SESSION的BLOCKING_SESSION和应用线程堆栈才能定位。











