insert语句与foreign key约束冲突

2 02 2 年,我在上海做ERP系统的时候……就遇到了这个。
INSERT语句直接报错,说FOREIGN KEY约束冲突。
我当时很困惑。
我查了半天日志,发现……Customer表中添加了一个新的ID 1 001 ,但是向Order表插入订单时,必须关联Customer ID 1 002 ,而这个1 002 根本不存在。

这就是冲突的原因。
FOREIGN KEY 用于管理引用完整性。
子表中的外键值必须在父表的主键中找到。
如果你找不到它,数据库不会让你列出它。
这是规则。
例如,如果您在 2 02 2 年 3 月下了一个新订单,如果客户 ID 没有找到此人,数据库将立即停止,并报错“客户表中找不到键值”。

解决办法是什么?
第一个也是最可靠的方法是插入一个原始表可以识别的值。
首先必须确保要插入的子表记录中的外键列值存在于父表中。
例如,必须事先添加客户 ID 和部门 ID。
2 02 2 年的上海项目,我们只是先运行脚本,将订单需要的所有客户批量录入Customers表中,然后批量录入订单,就搞定了。
您还可以使用交易。
使用同一事务插入父表和子表。
要么成功,要么回滚以确保数据一致性。
当时我们用MySQL创建事务启动,插入命令然后提交,相当稳定。

第二招是解绑外键。
但这东西必须要谨慎使用。
例如,在MySQL中,您可以使用SET FOREIGN_KEY_CHECKS=0暂时关闭检查,然后在插入数据后再次打开它们。
但这是一个临时解决方案,不能用于商业目的。
如果它已被永久删除,则这是不可能的。
使用 ALTER TABLE 直接删除条目。
2 02 2 年有一个项目想做这个,但是数据被篡改了。
该订单随后提供了一个不存在的客户 ID,使得整个报告都是虚假的。
最终只能将数据填写完整。
所以这个技巧不太推荐。

需要注意的事项,无非两点。
首先,继续使用第一种方法。
外键限制是保护数据的最佳方法。
如果可以的话,不取消就不要取消。
其次,如果你起床了要真正消除它,应用程序层必须自己找到一种方法,例如添加触发器或编写代码进行验证。
然而,这会很烦人。
我们在2 02 2 年取消了项目的限制后,花了三个月的时间开发来编写激励,这是非常昂贵的。

就是这样。

postgresql先执行删除大量数据 后再插入id一样的数据进去,提示主键冲突

说实话,这个问题我已经遇到过好几次了,每次都像在迷宫里抓鬼一样。
PostgreSQL 有时相当顽固,尤其是在主键冲突方面。
你总结得很好。
我会补充一些我遇到过的坑和实践经验。

无义务交易的陷阱 记得有一次,我在测试环境做实验时,运行了BEGIN;排除 ...;插入...;系列中。
结果插入的时候直接报主键冲突。
当时我还在想为什么删除数据后还是有冲突。
之后我回滚事务,再次尝试,发现序列值没有改变。
这是一个典型的未提交事务问题:为了保证数据一致性,PostgreSQL会锁定主键,直到事务提交或回滚。
因此,你必须承诺;删除操作后,或使用 SAVEPOINT 将段提交到事务中。

序列恢复详细信息 重置序列确实很复杂。
例如有一个users表,主键是id(SERIAL),删除1 00条数据后,sequence值可能仍然是1 01 此时插入id=1 的数据就会产生冲突。
我通常使用 SELECT MAX(id) FROM users;要检查最大值,然后使用 SELECT setval('users_id_seq', MAX(id));重置它。
然而,存在一个复杂的操作。
如果要插入的主键小于当前序列,可以手动更改序列值,例如 setval('users_id_seq', 0);然后输入id=1 的数据。
我同事第一次使用这个操作的时候,手在抖,直接setval(..., -1 );。
结果,序列陷入停滞,最终只能ROLLBACK;是可能的。
因此,在这一步之前最好先备份序列值。

约束和触发因素的隐形杀手 我曾经在删除数据时忘记检查外键约束,导致触发器在插入时报错。
例如,users 表依赖于 Orders 表的外键。
如果直接删除用户,再插入一个相同ID的新用户,会因为外键不存在而报错。
此时必须先删除子表数据或者使用ON DELETE CASCADE。
触发器更加令人困惑。
有一个 BEFORE INSERT 触发器来检查用户的状态。
删除用户后,新插入的具有相同ID的用户被触发器有效阻止。
因此,在清理数据之前,请确保使用 SELECT FROM pg_trigger WHERE tgname = 'xxx' AND tgrelid = 'users'::regclass;来控制触发器。

TRUNCATE 的隐藏好处 当删除整个表时,TRUNCATE 实际上比 DELETE 效率高得多。
上次我清理一个有2 00万行的表时,DELETE挂了近2 个小时,TRUNCATE不到1 0分钟就完成了,并且序列自动恢复了。
然而,有一个陷阱。
TRUNCATE 将锁定表。
如果该表依赖于其他操作,则有必要提前通知它。
此外,TRUNCATE 不能用于具有外键约束的表,除非子表也使用 TRUNCATE。

批量删除实践 我通常会批量删除大表。
例如,DELETE FROM users WHERE id > 1 000000,COMMIT after执行;然后是下一批。
这可以避免长事务阻塞表并监控进度。
一个技巧是可以使用 COPY 从临时表中导入数据,然后替换原始表,这比 DELETE 效率高得多。
但是,这是有风险的,需要保证临时表的结构完全一致。

最后,有点好奇:PostgreSQL 1 0之后,可以直接使用 ALTER SEQUENCE users_id_seq RESTART WITH 1 00;重置序列,无需首先检查最大值。
但是,最好在恢复之前检查所有数据是否已删除。
如果还有任何隐藏行,新数据可能仍然会发生冲突。