解决Hibernate更新时LockAcquisitionException锁等待超时问题
嘿,我之前也踩过类似的坑!你遇到的Lock wait timeout exceeded问题,核心原因就是你提到的——DAO层的HQL没加WHERE条件,直接更新了整个Product表,导致InnoDB锁了太多行,触发了超时。咱们一步步来搞定它:
问题到底出在哪?
当你执行不带WHERE子句的UPDATE Product SET deleteTimestamp = :deleteTime时,MySQL的InnoDB引擎会干两件坏事:
- 它会尝试锁定Product表的所有行(如果你的category外键没建索引,甚至会直接升级成表锁);
- 如果表数据量大,或者有其他并发操作在读写Product表,锁会被长时间占用,其他事务拿不到锁就超时了。
具体怎么修复?
1. 先把DAO层的HQL改对,只更新目标分类的产品
这是最关键的一步!必须给HQL加上分类过滤条件,只更新指定分类下的产品。假设你的Product实体里用productCategory.id关联分类ID,正确的HQL应该是:
UPDATE Product p SET p.deleteTimestamp = :deleteTime WHERE p.productCategory.id = :categoryID
如果你的Product直接关联了ProductCategory对象,也可以这么写:
UPDATE Product p SET p.deleteTimestamp = :deleteTime WHERE p.productCategory = :category
这样一来,只会锁定目标分类下的产品行,锁的范围一下子就缩小了,超时概率会大幅降低。
2. 给外键字段加索引,避免全表扫描
哪怕你加了WHERE条件,如果category_id外键没有索引,MySQL还是会全表扫描找目标行,这时候还是会锁很多不必要的行。所以一定要给Product表的category_id字段加索引:
用SQL直接建:
CREATE INDEX idx_product_category ON product(category_id);
或者在Hibernate实体里通过注解自动生成:
@Entity public class Product implements java.io.Serializable { // ...其他字段 @ManyToOne @JoinColumn(name = "category_id", nullable = false) @Index(name = "idx_product_category") // 加这个注解生成索引 private ProductCategory productCategory; // ... }
3. 缩短信务范围,别让锁拿太久
检查一下你的服务层和DAO层的事务边界:
- 别在事务里做无关的事,比如打大量日志、调用外部API这些,尽量让事务只包含更新操作,缩短锁的持有时间;
- 如果用Spring的话,调整
@Transactional的参数,比如设置合理的timeout,避免事务挂太久。
4. 分类下产品太多?试试分批更新
如果某个分类下有几万甚至几十万条产品,哪怕加了条件,一次性更新还是可能锁太多行。这时候可以分批更新,每次只更100条左右:
@Override public void softDeleteProductByCategory(int categoryID, Timestamp deleteTime, Session session) { int batchSize = 100; int updatedCount; do { Query updateQuery = session.createQuery("UPDATE Product p " + "SET p.deleteTimestamp = :deleteTime " + "WHERE p.productCategory.id = :categoryID " + "AND p.deleteTimestamp IS NULL"); // 确保只处理未软删的产品 updateQuery.setParameter("deleteTime", deleteTime); updateQuery.setParameter("categoryID", categoryID); updateQuery.setMaxResults(batchSize); updatedCount = updateQuery.executeUpdate(); } while (updatedCount > 0); }
每次只锁一小部分行,给其他操作留出空间,就不容易超时了。
最后验证一下
改完之后,再调用/softDeleteProductByCategory接口,你会发现锁的范围小了,持有时间短了,再也不会出现锁等待超时的问题啦!
内容的提问来源于stack exchange,提问作者PeakGen

