MySQL 导入 INSERT INTO 时出现 PRIMARY 重复错误
结论
#1062 - Duplicate entry '1' for key 'PRIMARY' 与外键无关。这个错误表示目标表中已经有一条主键为 1 的记录,而下面的语句又要插入相同的主键:
INSERT INTO payment VALUES (1, 1, 'TAK');
如果第一列是 AUTO_INCREMENT 主键,插入数据时应省略这一列,或者传入 NULL,由 MySQL 自动生成新值。建议同时明确写出列名。
先确认表中已有的数据
请使用数据库中的实际表名,例如 payment 或 płatnosc:
SELECT *
FROM `sklep`.`payment`
WHERE id = 1;
如果实际的表名和主键名是 płatnosc.id_płatnosci,则执行:
SELECT *
FROM `sklep`.`płatnosc`
WHERE `id_płatnosci` = 1;
还可以查看完整的建表结构:
SHOW CREATE TABLE `sklep`.`płatnosc`;
只要查询结果中已经有一条主键为 1 的记录,再次插入主键 1 就会触发 1062。
常见原因有:
- 同一份数据被导入了两次。
- 表中原本就有测试数据。
- 删除数据库或清空表的操作没有成功执行。
- SQL 文件中的多条
INSERT使用了相同的主键。 CREATE TABLE IF NOT EXISTS遇到已有表时不会重建表,原来的表和数据仍会保留。
推荐的插入方式
假设支付表的第一列是自增主键,应明确列出其他字段:
INSERT INTO `sklep`.`płatnosc` (`业务字段1`, `业务字段2`)
VALUES (1, 'TAK');
其中的列名需要替换为支付表中的真实列名。例如,表结构如下:
CREATE TABLE `płatnosc` (
`id_płatnosci` INT NOT NULL AUTO_INCREMENT,
`id_zamowienie` INT NOT NULL,
`status` VARCHAR(10) NOT NULL,
PRIMARY KEY (`id_płatnosci`)
);
对应的插入语句可以写成:
INSERT INTO `sklep`.`płatnosc` (`id_zamowienie`, `status`)
VALUES (1, 'TAK');
MySQL 会自动生成 id_płatnosci。
也可以显式传入 NULL:
INSERT INTO `sklep`.`płatnosc`
VALUES (NULL, 1, 'TAK');
不过,这种写法依赖当前的列顺序。以后增加字段或调整字段顺序时容易出错,因此更推荐明确写出列名。
如果必须保留导出文件中的主键
迁移数据时,订单或其他表可能通过外键引用固定的支付 ID。此时不能直接省略主键,必须先确认目标表中不存在冲突数据。
如果目标数据库本应是全新的空数据库,可以先删除并重新创建:
DROP DATABASE IF EXISTS `sklep`;
CREATE DATABASE `sklep`;
USE `sklep`;
然后按以下顺序导入:
- 创建所有表。
- 导入被引用的父表,例如客户、支付方式等。
- 导入包含外键的子表,例如地址、订单。
- 最后导入订单明细等更下层的数据。
如果只需清空某张表,可以使用:
TRUNCATE TABLE `sklep`.`płatnosc`;
如果其他表通过外键引用了这张表,TRUNCATE 可能会被拒绝。这时应先清理引用它的子表,或者按照外键依赖顺序重新初始化数据库。不要为了避开错误而随意关闭外键检查,否则可能导入无法对应的孤立数据。
如果现有主键 1 对应的正是所需记录,就不应再次插入。检查数据后,可以选择更新:
UPDATE `sklep`.`płatnosc`
SET `业务字段1` = 1,
`业务字段2` = 'TAK'
WHERE `id_płatnosci` = 1;
也可以改用新的主键:
INSERT INTO `sklep`.`płatnosc`
(`id_płatnosci`, `业务字段1`, `业务字段2`)
VALUES
(2, 1, 'TAK');
不建议直接使用 INSERT IGNORE 或 REPLACE
下面的写法可能不再显示 1062,但往往只是把问题隐藏起来:
INSERT IGNORE INTO `płatnosc` VALUES (1, 1, 'TAK');
发生主键冲突时,这一行可能被直接跳过,导致最终导入的数据不完整。
REPLACE 的风险更大:
REPLACE INTO `płatnosc` VALUES (1, 1, 'TAK');
出现冲突时,它通常相当于先删除旧行,再插入新行,可能引发外键、删除触发器或其他关联问题。如果确实需要”存在则更新”,应明确写出更新逻辑:
INSERT INTO `płatnosc`
(`id_płatnosci`, `业务字段1`, `业务字段2`)
VALUES
(1, 1, 'TAK')
ON DUPLICATE KEY UPDATE
`业务字段1` = VALUES(`业务字段1`),
`业务字段2` = VALUES(`业务字段2`);
这种写法适合有意进行覆盖式同步的场景,不能作为修复错误数据库设计的通用办法。
当前建表语句中还需要调整的地方
外键约束名称重复
adres 和 zamowienie 中都定义了名为 id_klienta 的外键约束:
CONSTRAINT `id_klienta`
在部分 MySQL 或 MariaDB 版本中,同一数据库内的外键约束名称必须唯一。即使当前环境允许,也不建议让约束名与列名完全相同。可以改成包含表名的名称:
CONSTRAINT `fk_adres_klient`
FOREIGN KEY (`id_klienta`)
REFERENCES `sklep`.`dane_klient` (`id_klient`)
订单表可以使用:
CONSTRAINT `fk_zamowienie_klient`
FOREIGN KEY (`id_klienta`)
REFERENCES `sklep`.`dane_klient` (`id_klient`)
支付外键可以改成:
CONSTRAINT `fk_zamowienie_platnosc`
FOREIGN KEY (`id_platnosci`)
REFERENCES `sklep`.`płatnosc` (`id_płatnosci`)
为外键列建立索引
zamowienie.id_klienta 没有显式声明索引。MySQL/InnoDB 在某些情况下会自动创建所需索引,但最好在表结构中明确写出:
INDEX `idx_zamowienie_klient` (`id_klienta`)
避免订单和支付互相引用
如果 zamowienie 引用 płatnosc,同时 płatnosc 又引用 zamowienie,就会形成循环依赖,增加建表和数据导入的难度。
通常只需保留一个引用方向。例如,让支付记录引用订单:
płatnosc.id_zamowienie -> zamowienie.id_zamowienie
此时不再需要:
zamowienie.id_platnosci -> płatnosc.id_płatnosci
如果一个订单允许多次支付尝试,这种设计也更自然,因为一条订单可以对应多条支付记录。
建议的排查顺序
先查看实际的表结构和已有数据:
SHOW CREATE TABLE `sklep`.`płatnosc`;
SELECT * FROM `sklep`.`płatnosc`;
然后检查主键是否重复:
SELECT `id_płatnosci`, COUNT(*) AS `cnt`
FROM `sklep`.`płatnosc`
GROUP BY `id_płatnosci`
HAVING COUNT(*) > 1;
主键约束正常生效时,表内不允许存在重复值,所以这条查询通常不会返回结果。更常见的情况是 SQL 文件多次尝试插入同一个主键值。
最后,检查 SQL 文件中是否重复出现:
INSERT INTO `płatnosc` VALUES (1, ...);
如果这是日常新增数据,应省略自增主键。如果这是完整的数据迁移,应使用空数据库重新导入,并确保每个主键值只出现一次。
备注:内容仅供参考。
