ThinkPHP 6.0 不原生封装 ON DUPLICATE KEY UPDATE,但可通过 Db::execute() 或 Db::query() 手动执行该语法实现 upsert;前提是 MySQL 表必须有主键或唯一索引,否则仅作普通 INSERT 并报冲突错误。

ThinkPHP 6.0 本身不原生封装 ON DUPLICATE KEY UPDATE 语法,但完全支持通过原生 SQL 或 Query Builder 手动构造该语句实现 upsert,前提是底层数据库为 MySQL(或兼容的 MariaDB)。关键在于:必须有主键或唯一索引作为冲突判断依据,且需主动编写符合 MySQL 规范的 INSERT ... ON DUPLICATE KEY UPDATE 语句。
需要先确保表结构有唯一约束
这是触发 ON DUPLICATE KEY UPDATE 的前提。没有主键或唯一索引,该语法不会生效,只会当作普通 INSERT 执行(重复时直接报错)。
- 例如用户表需在
username或email字段上建唯一索引:ALTER TABLE user ADD UNIQUE (email); - 主键(如
id)天然满足条件;若用自增主键做 upsert,通常按id冲突更新,但更常见的是按业务唯一字段(如手机号、订单号)操作
TP6 中执行 upsert 的两种常用方式
推荐优先使用 Db 类的原生查询,清晰可控;Query Builder 暂不提供链式 upsert 方法,需拼接 SQL。
-
方式一:Db::execute() 执行原生 SQL
直接传入完整语句,支持VALUES()函数引用插入值:Db::execute("INSERT INTO user (id, name, email) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email)", [$id, $name, $email]); -
方式二:Db::query() + 参数绑定(更安全)
避免 SQL 注入,尤其适合变量来自用户输入:Db::query("INSERT INTO user (email, status) VALUES (:email, :status) ON DUPLICATE KEY UPDATE status = VALUES(status)", ['email' => $email, 'status' => $status]);
批量 upsert 更高效,但要注意语法细节
一次插入多行并各自触发冲突判断,MySQL 会逐行检查唯一键,分别执行插入或更新,返回总影响行数(插入行数 + 更新行数)。
- VALUES 列表用逗号分隔,
ON DUPLICATE KEY UPDATE子句只写一次,作用于所有行:INSERT INTO user (email, score) VALUES ('a@b.com', 80), ('c@d.com', 95) ON DUPLICATE KEY UPDATE score = VALUES(score); - TP6 中同样用
Db::execute()或Db::query()执行,参数可传二维数组或展开为占位符
更新逻辑可结合表达式与条件控制
不只是简单赋值,还能做累加、时间戳更新、甚至条件判断(需借助 MySQL 函数)。
- 累加计数:
score = score + VALUES(score) - 更新时间戳:
update_time = NOW()或update_time = VALUES(update_time) - 避免无意义更新(值未变时不触发 update):
MySQL 5.7+ 支持IF(VALUES(col) != col, VALUES(col), col),但需注意 NULL 处理
不复杂但容易忽略:TP6 的事务包装、异常捕获和返回值处理要配套做好。执行后可通过 Db::getRealSql() 调试生成的 SQL,确认是否符合预期。


















