hyperf协程中全表delete必须显式事务包裹、分批执行并避免truncate。因默认无自动事务,裸删会加剧undo log膨胀;协程复用连接导致truncate重置连接引发“server has gone away”;外键需手动控制禁用与恢复且须同连接成对执行。

直接执行 DELETE FROM table_name 不加 WHERE 会清空全表,但不是最安全、最高效的方式;在 Hyperf + 协程环境下,更需警惕事务阻塞、锁等待和日志膨胀问题。
协程中执行 DELETE 全表必须显式开启事务
Hyperf 默认关闭自动事务,DELETE 若不在事务内执行,每行删除都会单独提交,极大增加 undo log 体积和 I/O 压力。尤其在高并发协程场景下,可能触发磁盘写满或事务超时。
- 务必用
$this->db->transaction()包裹,哪怕只删一张表 - 避免在
go()协程里裸调delete(),协程调度可能导致连接复用混乱 - 若用
Db::delete(),确认它底层是否已封装事务 —— 大多数情况下没有
示例:
go(function () {
$db = Hyperf\DbConnection\Db::connection();
$db->transaction(function ($txn) {
$txn->delete('logs', ['created_at' => ['statement('DELETE FROM logs WHERE created_at
<h3>大表全删必须分批 + sleep 避免锁表</h3>
<p>Hyperf 的协程 MySQL 客户端(如 Swoole MySQL)不支持真正的异步阻塞等待,<code>DELETE ... LIMIT</code> 若不控制节奏,会瞬间压垮连接池和主库 CPU。</p>
- 单次
LIMIT建议 ≤ 5000 行,配合usleep(10000)(10ms)让出协程调度权 - 不能依赖
while ($affected = $stmt->execute()) && $affected > 0无限循环 —— 协程没 yield 会导致其他请求饿死 - 用
Db::select()先查总行数,预估循环次数,防止误删失控
关键逻辑节选:
$total = $this->db->fetchOne('SELECT COUNT(*) FROM huge_table WHERE status = ?', ['pending']);
$batch = 2000;
for ($i = 0; $i db->delete('huge_table', ['status' => 'pending'], [], $batch);
usleep(10000); // 必须有,否则协程抢占失衡
}
TRUNCATE 不可用:协程连接无法执行 DDL
Hyperf 的 MySQL 连接池基于长连接复用,而 TRUNCATE TABLE 是 DDL 操作,会隐式提交当前事务并重置连接状态。在协程中执行会导致:
- 报错
MySQL server has gone away(因连接被重置) - 后续同连接的查询拿到过期的表元数据,返回空结果或结构错乱
- 连接池中的该连接被标记为 invalid,但未及时剔除,引发后续请求失败
所以即使只是清空数据,也必须用 DELETE + WHERE 1=1,而非 TRUNCATE。若真要 TRUNCATE,只能换用独立 CLI 进程(如 proc_open 调 mysql -e "TRUNCATE ..."),但失去事务一致性保障。
外键约束下 DELETE 全表要手动处理顺序
Hyperf 不会自动解析外键依赖关系。若表有外键引用,直接 DELETE FROM parent 会报错 Cannot delete or update a parent row。
- 先禁用检查:
$this->db->statement('SET FOREIGN_KEY_CHECKS = 0')(需在事务外执行) - 再按子表 → 父表顺序逐个
DELETE,否则仍可能违反约束 - 最后恢复:
$this->db->statement('SET FOREIGN_KEY_CHECKS = 1') - 注意:禁用外键检查期间,所有写入都绕过完整性校验,仅限维护窗口内使用
真正危险的点在于:Hyperf 的连接池可能把 SET FOREIGN_KEY_CHECKS 绑定到某个连接上,而后续请求复用该连接却不知情 —— 所以必须在同连接内成对执行,且避免跨协程共享连接句柄。











