背景
为应对最后上线测试,在 UAT 环境进行一次总量3000万的数据集成,数据分为4种DataSorce.在执行到第三种DataSource时,集成失败.并收到告警邮件.提醒如下报错.讲这个报错前.就得下说一下当前数据集成方案..
### Cause: PSQLException: ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
建议:Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
; ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
建议:Ensure that no rows proposed for insertion within the same command have duplicate constrained values.; nested exception is org.postgresql.util.PSQLException: ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
建议:Ensure that no rows proposed for insertion within the same command have duplicate constrained values., 2025-06-18 14:31:12.729, 2025-06-18 15:50:03.329, 4213015, 20250618143057241, 1750227637384308, 2, 2025-06-18 17:10:06.947774, 2025-06-19 09:25:29.999212, 0, 2025-06-19 10:20:39.607914, 2025-06-19 10:36:15.499851
<== Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4f49b57a]
当前集成方案 (演示)
INSERT INTO master_order (order_id, order_info, product_info, geo_info)
SELECT A.order_id,
A.order_id,
P.product_info,
G.geo_info
FROM
transaction_table
A LEFT JOIN product_table P ON A.product_no = P.product_no
LEFT JOIN geo_table G ON A.country_code = G.country_code
WHERE
version_number = '001' ON CONFLICT (order_id) DO
UPDATE
SET order_info = excluded.order_info,
product_info = excluded.product_info,
geo_info = geo_info.geo_info
这里使用的是 ON CONFLICT DO UPDATE 函数. 又称 UPSERT 方式,即通过主键判断: 当记录不存在 执行插入,否则进行更新!
建议:在做大数据量数据处理时.要优先使用UPSERT方式,避免使用先删除再插入方式。先删除在插入处理大数据量时性能不好,造成大量索引重建。数据页分裂,大量空间碎片等问题.
UPSERT 使用限制
1.CONFLICT(字段1,字段2) 必须为唯一主键
upsert 使用的字段必须要主键索引(PK)。必须主键PK. 二级索引都不行.
2.更新的数据源主键不允许重复
什么意思呢? 由于master_order 中 order_id 是唯一主键。就必须要求数据源SQL的结果中不允许有重复的 KEY(order_id).否则就报错. PostgreSQL不会主动去处理这种报错, 需要用户自己来保障。属于用户责任.
// 这个结果必须要唯一,一个order_id 只能有一条数据
SELECT A.order_id,
A.order_id,
P.product_info,
G.geo_info
FROM
transaction_table
A LEFT JOIN product_table P ON A.product_no = P.product_no
LEFT JOIN geo_table G ON A.country_code = G.country_code
WHERE
version_number = '001'
问题分析
第一条!满足.
第二条!系统开发文档明明记录着 transaction_table 表 order_id 主键,product_table 表 product_no 是主键, geo_table 表 country_code 主键。所以应该也没问题啊!那为啥还报错?马上给Pgsql 提 issue,发现bug 了......
最后检查发现,不知道哪位勇猛的好战友将 product_table 表中 product_no 主键去掉了。然后插入了两条一模一样的产品编号数据.在关联查询中就导致了一个 order_id 对应了多条订单数据。
解决:赶紧删除冗余数据.重新为product_table 表设置 product_no 主键索引.手动重启集成。危机解除!
。。可惜了,只是UAT ,应该把生产环境都改咯才是好!