跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

为什么Hyperf模型批量更新非常慢_改用原生InsertOnDuplicateKey语句

根本原因是Model::upsert()在Hyperf中仍走模型生命周期、未合并SQL且默认查库判断冲突;应改用Db::insert()配合ON DUPLICATE KEY UPDATE原生语句,确保唯一索引、字段一致、分片控制,并手动包裹事务。 Hyperf模型批量更新慢,根本原因不是数据库本身,而是
Model::update()
或
Model::upsert()
这类方法在内部仍走完整模型生命周期——每条数据都实例化、校验、触发事件、拼SQL、再单条执行。哪怕你传入1000条,它默认仍是N次查询或N次独立UPDATE。要真正提速,必须绕过模型层,直连
Db::insert()
配合
ON DUPLICATE KEY UPDATE
语句。 为什么Model::upsert()在Hyperf里还是慢
Model::upsert()
看似是批量接口,但在Hyperf 3.x中它底层仍会尝试先
SELECT
查是否存在(尤其没指定
$uniqueBy
时),再决定INSERT或UPDATE;更关键的是,它不合并成一条SQL,而是拆成多条单独语句执行。实测万级数据下,耗时可能比手写原生语句高5–8倍。 它会为每条数据创建
Model
实例,触发
booting
、
setAttribute
、
casts
等开销 字段映射依赖
$fillable
和
$casts
,动态处理进一步拖慢速度 若没显式传
$uniqueBy
参数,Hyperf会尝试读取主键+唯一索引,额外查information_schema 并发环境下,多个协程共用同一连接池但未复用PDO预编译句柄,导致prepare重复开销 怎么用Db::insert() + ON DUPLICATE KEY UPDATE写批量更新 核心是把更新逻辑“反向”写成「插入语句 + 冲突时更新」,MySQL原生支持,一条SQL搞定多行。前提是表必须有
PRIMARY KEY
或
UNIQUE
索引(比如
id
或
user_id
)。 Hyperf 3.1.66 本页面提供企业级 PHP 协程框架 Hyperf 3.1.66 版本的官方源码下载与完整更新日志。重点解析 v3.1.66 版本中新增的 gRPC 多客户端负载均衡支持、Pool 连接池全量刷新、Guzzle 持久化 Cookie 以及数据库 JSON 包含键查询等核心优化特性。 下载 构造二维数组,确保每项键名与数据库字段完全一致:
['id' => 1, 'sort' => 7]
,不能是
['user_id' => 1]
而字段叫
id
调用
Db::table('your_table')->insert($batch)
不行——它不支持
ON DUPLICATE KEY UPDATE
;必须用
Db::insert()
并手动拼SQL 正确写法是:
Db::insert("INSERT INTO your_table (id, sort, updated_at) VALUES ? ON DUPLICATE KEY UPDATE sort = VALUES(sort), updated_at = VALUES(updated_at)", [$values])
,其中
$values
是
array_merge(...$batch)
展平后的索引数组 注意
VALUES(sort)
引用的是当前这一行的输入值,不是原表值;如需保留原值(比如只更新
sort
不碰
updated_at
),就写
updated_at = updated_at
容易踩的三个坑 这三个问题不解决,哪怕语句写对了,照样报错或静默失败。
SQLSTATE[HY093]: Invalid parameter number
:常见于
$batch
里混用关联数组和索引数组,或字段顺序不统一。必须保证所有子数组键名一致、顺序一致,且不含空值或null字段(除非对应列允许NULL) “更新没生效”,实际是唯一索引没建对:检查是否真在目标字段(如
id
)上建了
UNIQUE
或
PRIMARY KEY
;如果用
user_id
做冲突判断,但表只有
id
主键,那
ON DUPLICATE KEY UPDATE
永远不触发 超长报错
Packets larger than max_allowed_packet are not allowed
:单次最多塞1000行左右。分片用
array_chunk($data, 500)
,别硬刚2000+;同时确认MySQL配置
max_allowed_packet
≥ 64M 最常被忽略的一点:Hyperf里
Db::insert()
不自动开启事务。如果你的批量更新需要原子性,必须手动
Db::transaction()
包裹,否则某一批失败,前面成功的不会回滚。另外,
VALUES()
函数在MySQL 5.7+才完全稳定,线上用8.0+更稳妥。

相关文章