Hibernate关联Vertica多线程更新依赖实体出现全表锁及死锁问题
排查步骤
- 验证关联字段索引存在性:Children表的
father_id字段为外键关联字段,若未创建索引,Vertica在处理子表关联查询、写入操作时会执行全表扫描,在SERIALIZABLE隔离级别下全表扫描会直接触发表级锁,即使操作不同行也会出现锁冲突。另外配置了CascadeType.ALL,mergeFather对象时会自动操作关联的Children记录,无索引的情况下全表扫描会直接锁整个Children表,连带触发Father表的锁升级。 - 校验事务隔离级别适配性:SERIALIZABLE是最高隔离级别,Vertica在该级别下会对所有读写的范围加间隙锁,无索引限定时扫描范围会覆盖整张表,不同事务的锁范围重叠就会触发死锁。
- 核对Hibernate实际执行SQL:开启
show_sql配置打印执行的SQL,确认merge操作前是否存在全表查询,是否命中father_id索引。当前配置的FetchMode.SUBSELECT加载Children时会生成子查询,无索引时子查询也会触发全表扫,进而触发表锁。 - 查询Vertica系统锁日志:查询系统表
LOCKS、DEADLOCKS,确认锁的类型、触发SQL以及锁覆盖范围,定位具体加锁逻辑。
修复方案
- 新增关联字段索引:在Children表的
father_id字段上创建索引,可直接在数据库执行创建索引语句,也可在实体类fatherId属性上添加@Index注解自动生成。索引创建后操作子表时只会扫描对应father_id的行,仅加行锁不会触发表锁。 - 降低事务隔离级别:若业务不需要SERIALIZABLE级别的一致性保证,可将隔离级别调整为
READ COMMITTED,该级别下Vertica仅对实际修改的行加锁,不会加范围锁,大幅降低锁冲突概率。 - 优化Hibernate关联配置:将
FetchType.EAGER改为FetchType.LAZY,仅在需要使用Children数据时才查询,减少不必要的表扫描。根据实际业务需求调整CascadeType配置,不需要的级联操作可以移除,减少不必要的关联表写入。 - 缩小事务粒度:精简
update方法内的逻辑,移除事务中不需要的非数据库操作,缩短事务执行时长,降低锁冲突的概率。 - 调整加载策略:如果每次仅操作单个Father对象,可将
FetchMode.SUBSELECT改为FetchMode.JOIN,避免子查询全表扫描带来的额外锁开销。
内容的提问来源于stack exchange,提问作者John Humanyun
相关产品推荐
相关产品推荐

