ThinkPHP中ON DUPLICATE KEY UPDATE必须用Db::execute()执行原生SQL,因ORM的save()/insert()不支持该语法且会报主键冲突错误;需确保主键或唯一索引存在、字段出现在INSERT和UPDATE子句中,并用参数绑定防注入。

ThinkPHP 里 ON DUPLICATE KEY UPDATE 不能直接用 save() 或 insert()
ThinkPHP 的 ORM 方法(比如 save()、insert())底层不支持 MySQL 的 ON DUPLICATE KEY UPDATE 语法,强行传入 duplicate 参数或拼 SQL 字符串,大概率触发主键冲突异常(如 SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry),而不是走更新逻辑。
根本原因是 TP 默认把重复主键/唯一键当作错误处理,而非业务分支。想走“存在则更新,不存在则插入”,必须绕过 ORM 的自动判断,改用原生 SQL 或底层查询构造器。
- 别在
Db::table()->insert()后加 try-catch 捕获 1062 错误再手动 update —— 并发下可能漏更新或重复插入 -
replaceInto()看似可行,但它本质是REPLACE INTO(先删后插),会丢失自增 ID、触发 DELETE 钩子、破坏外键约束,不是等价替代 - TP 6.0+ 的
strict模式默认开启,对主键冲突更敏感,错误更早暴露
用 Db::execute() 写原生 INSERT ... ON DUPLICATE KEY UPDATE
最稳的方式是放弃 ORM 插入方法,改用 Db::execute() 执行完整 SQL。这样能完全控制语法,也避免框架对返回值的误判。
注意字段名和值要严格对应,且主键或唯一索引字段必须在 INSERT 子句中出现,否则 ON DUPLICATE KEY 不生效。
立即学习“PHP免费学习笔记(深入)”;
- 主键字段(如
id)或唯一索引字段(如email)必须出现在INSERT的列列表和 VALUES 中 - 更新部分(
UPDATE后)只能写字段 = VALUES(字段),不能写函数或子查询(除非你确认 MySQL 版本支持) - 字符串值必须用
addslashes或参数绑定防注入;推荐用Db::execute()的参数占位方式,例如:Db::execute('INSERT INTO user (id, name) VALUES (?, ?) ON DUPLICATE KEY UPDATE name = ?', [$id, $name, $name]) - 执行成功返回影响行数:0(无变更)、1(新增)、2(更新)—— 可据此做后续判断
Db::query() 和 Db::execute() 选哪个?
Db::query() 用于 SELECT,返回结果集;Db::execute() 用于 INSERT/UPDATE/DELETE,返回影响行数。这里必须用 Db::execute(),否则拿不到实际影响行数,也无法判断是插入还是更新。
- 用
Db::query()执行INSERT ... ON DUPLICATE KEY UPDATE会报错或返回空数组,因为不是查询语句 - TP 的
getLastInsID()在ON DUPLICATE KEY UPDATE场景下不可靠:插入时返回新 ID,更新时返回 0 或上一次插入 ID,不能作为业务依据 - 如果需要返回更新后的整行数据(比如带时间戳的
updated_at),得额外查一次:Db::table('user')->where('id', $id)->find()
唯一索引字段没建对,ON DUPLICATE KEY 就是摆设
这个语法只响应主键(PRIMARY KEY)或唯一索引(UNIQUE INDEX)冲突。如果表结构里只有普通索引(INDEX)或没建任何唯一约束,MySQL 根本不会触发 ON DUPLICATE KEY 分支,而是直接报错。
- 检查建表语句:确认冲突字段上有
UNIQUE或PRIMARY KEY,例如email VARCHAR(100) UNIQUE - 复合唯一索引也支持,但
INSERT语句中必须包含全部索引字段,否则不匹配 - TP 迁移命令
Schema::table()加唯一索引时,记得用unique()方法,不是index() - 开发环境用
SHOW CREATE TABLE user确认索引类型,别只看代码里的 migration 文件
主键冲突的本质是数据库约束层面的问题,框架只是通道。没对齐索引定义,再怎么调用 Db::execute() 都只会报错。



















