主键冲突需在sql层用on duplicate key update显式处理,不可依赖php;其update部分受限于mysql语法,须用values(col_name)引用新值、ifnull处理null;thinkphp中仅db::execute()支持,orm方法会抛异常;insert ignore静默丢弃但自增id跳变;字符集与字段截断亦可致隐性重复。

主键冲突不是 PHP 的问题,而是你没在 SQL 层明确告诉 MySQL「重复时想怎么处理」。直接 INSERT 必然报错 1062,必须用原生 SQL 显式声明策略——PHP 本身不参与冲突判断,只负责把正确的语句发过去。
ON DUPLICATE KEY UPDATE 是生产环境首选,但字段更新逻辑容易写错
它真正实现「有则更新、无则插入」,但 UPDATE 部分不是自由表达式,受 MySQL 语法严格限制:
-
VALUES(col_name)只能在UPDATE子句里引用插入时传入的值,不能写成id = VALUES(id) + 1这种非法形式 - 如果字段原值是
NULL,直接写count = count + 1结果仍是NULL,得用count = IFNULL(count, 0) + 1 - 只要任意一个唯一约束(主键或任一
UNIQUE KEY)命中,就触发更新;但只更新你SET列出的字段,其他字段不变 - 返回影响行数:1 表示新增,2 表示更新(MySQL 特性),可据此做后续分支
ThinkPHP 中必须用 Db::execute(),ORM 的 insert() 和 save() 不支持
TP 的 ORM 方法会把主键冲突当异常抛出,而不是走更新逻辑。哪怕你拼了 ON DUPLICATE KEY UPDATE 字符串传给 insert(),也会报 SQLSTATE[23000]: Integrity constraint violation: 1062:
- 必须用
Db::execute('INSERT INTO ... ON DUPLICATE KEY UPDATE ...', [$params]) - 别用
Db::query(),它只处理SELECT,执行INSERT类语句会失败或返回空 -
replaceInto()是REPLACE INTO,本质是DELETE + INSERT,自增 ID 跳变、触发器执行两次、外键可能断裂,不是等价替代 -
getLastInsID()在此场景下不可靠:更新时返回 0 或上一次插入 ID,不能用于业务判断
INSERT IGNORE 适合「只进不出」场景,但自增值会跳号
它最轻量,冲突时静默丢弃整行,不报错也不更新,但副作用明显:
- 只响应
PRIMARY KEY和UNIQUE KEY,对普通索引无效 - 被忽略的插入仍会消耗一个
AUTO_INCREMENT值,比如当前是 100,插入 id=101 失败后,下次正常插入 id 自动变成 102,中间空一个 - 不会触发
ON UPDATE CURRENT_TIMESTAMP这类自动时间戳更新 - 返回受影响行数为 0,可用于判断是否被忽略,但无法区分是「真没插」还是「被忽略」
真正容易被忽略的是字符集和字段截断问题:定义 VARCHAR(10) 却插入超长字符串,MySQL 截断后存入,多次不同输入可能产生相同截断结果,查不到但索引认为重复;还有 utf8mb4_unicode_ci 和 utf8mb4_0900_as_cs 对大小写/重音的判定差异,也会让「看起来不同」的值在唯一索引下被判为重复。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











