如何使用Hibernate删除Vertica表中复合主键相同的多行数据
Vertica作为列式分析数据库,默认的主键、唯一约束属于优化器提示约束,不会在写入时做强制唯一性校验,仅用于辅助查询优化器生成执行计划,因此即使建表时定义了复合主键,依然可以正常插入主键值完全相同的重复行,不会抛出主键冲突错误。
可以通过Hibernate实现该需求,但要注意避开默认删除逻辑的坑:不要直接调用session.delete()方法按映射的复合主键执行删除——该方法生成的SQL会按复合主键做全匹配,会把同主键下的所有行全部删除,无法实现「留1行删剩余重复行」的诉求。推荐以下两种可落地的实现方式:
方案一:原生SQL配合内置
ROW_ID伪列删除(性能最优,推荐大数据量场景使用)
Vertica为每一行存储的数据提供了全局唯一的内置伪列ROW_ID,和业务字段无关,可以唯一标识每一行,哪怕业务主键完全重复。不需要修改现有Hibernate实体映射,直接通过原生SQL执行去重删除即可,示例代码:Transaction tx = session.beginTransaction(); // 替换表名、复合主键列名为实际业务字段,逻辑为每组重复主键保留ROW_ID最小的1行,删除其余重复行 String delDupSql = """ DELETE FROM 你的业务表名 WHERE ROW_ID NOT IN ( SELECT MIN(ROW_ID) FROM 你的业务表名 GROUP BY 复合主键字段1, 复合主键字段2, 复合主键字段N ) """; session.createNativeQuery(delDupSql).executeUpdate(); tx.commit();该方案直接在数据库侧完成去重,不需要把数据拉到JVM内存处理,性能最适合Vertica这类存储TB级数据的分析场景。
方案二:ORM映射
ROW_ID后逐组删除(适合需要在删除前做额外业务校验的场景)
如果需要在删除重复行前做额外的业务逻辑判断,可以先在Hibernate实体类中映射只读的ROW_ID字段,再按重复主键分组逐组处理:- 首先在实体类中增加字段映射:
// 映射Vertica内置ROW_ID,设置为不可插入、不可更新 @Column(name = "ROW_ID", insertable = false, updatable = false) private String verticaRowId; - 执行去重逻辑:
Transaction tx = session.beginTransaction(); // 第一步:查询所有存在重复数据的复合主键分组 List<Object[]> dupKeyGroups = session.createNativeQuery(""" SELECT 复合主键字段1, 复合主键字段2 FROM 你的业务表名 GROUP BY 复合主键字段1, 复合主键字段2 HAVING COUNT(*) > 1 """).getResultList(); // 第二步:遍历每个重复组,保留1行,删除其余行 for (Object[] key : dupKeyGroups) { List<String> groupRowIds = session.createNativeQuery(""" SELECT ROW_ID FROM 你的业务表名 WHERE 复合主键字段1 = :key1 AND 复合主键字段2 = :key2 ORDER BY ROW_ID """) .setParameter("key1", key[0]) .setParameter("key2", key[1]) .getResultList(); // 跳过索引为0的第一行(保留行),从第二行开始删除 for (int i = 1; i < groupRowIds.size(); i++) { session.createNativeQuery("DELETE FROM 你的业务表名 WHERE ROW_ID = :rid") .setParameter("rid", groupRowIds.get(i)) .executeUpdate(); } } tx.commit();
- 首先在实体类中增加字段映射:
清理完存量重复数据后,如果需要从根源避免后续写入重复主键行,可以执行DDL开启主键约束强制校验:
-- 替换约束名为表上实际的复合主键约束名 ALTER TABLE 你的业务表名 ALTER CONSTRAINT 你的复合主键约束名 ENABLED;
开启后Vertica会在写入时校验主键唯一性,插入重复值时会直接抛出主键冲突错误,和传统OLTP数据库的主键行为一致。
内容的提问来源于stack exchange,提问作者Sneha Patro

