大事务会让mysql变慢,因其持续持有行锁、间隙锁甚至表锁,导致其他请求排队;同时undo log持续增长、mvcc版本链拉长,使快照读需遍历更长历史版本,连select都变卡。

为什么大事务会让MySQL变慢
大事务不是“执行时间长”才慢,而是它会持续持有行锁、间隙锁甚至表级资源,导致其他请求排队等待。更隐蔽的是:事务开启后,MySQL 的 undo log 持续增长,MVCC 版本链拉长,后续快照读(如 SELECT)可能要遍历更长的历史版本,连只读查询都变卡。你看到的“接口超时”,往往不是 PHP 在跑逻辑,而是卡在 SELECT 等锁,或 COMMIT 时刷盘写 undo/redo 太重。
拆分事务:每100条一个 commit,别全包进一个 Db::transaction
ThinkPHP5 默认用 Db::transaction 包整个操作,但批量导出、同步、补单这类场景,动辄几千上万条记录,全塞进去就是自找死锁和超时。必须手动切片:
- 用
for或while分批,每次取limit 100数据,处理完立即Db::commit() - 确保每批都在独立事务内完成:
Db::startTrans()→ 批量操作 →Db::commit(),别跨批次复用事务句柄 - 避免在循环里做
sleep()或file_put_contents()—— 这些不参与事务,但会拖长单次事务生命周期
事务内禁用 lock(true) + 避免 SELECT FOR UPDATE 全表扫描
lock(true) 是行锁没错,但如果 WHERE 条件没走索引,InnoDB 会退化为全表扫描+给所有扫描行加锁,等于事实上的表锁。尤其在分批处理时,若每批都执行类似 Db::name('order')->where('status', 0)->lock(true)->select(),而 status 列没索引,后果比不用锁还糟:
- 先用
EXPLAIN确认加锁查询是否走了索引,重点看type是否为ref或range,key是否显示实际索引名 - 对高频加锁字段(如
status、is_locked)单独建索引,或与主键组成复合索引 - 高并发更新同一张表时,优先考虑乐观锁(
version字段),避开数据库层锁竞争
关闭调试模式 + 禁用 SQL 日志是硬性前提
开发环境开着 app_debug=true 和 sql_debug=true 跑大事务,等于让框架每执行一条 SQL 就做一次日志拼接、Trace 记录、内存 dump —— 这些操作本身不进事务,但严重拖慢事务内语句执行节奏。生产环境必须确认:
-
APP_DEBUG=false(对应config/app.php或.env) -
'sql_debug' => false,且未设置SQL_DEBUG_LOG环境变量 - 移除中间件中对
think\middleware\Trace或自定义日志中间件的全局注册
事务范围缩小、索引到位、调试关闭——这三件事不做,调任何 innodb_log_file_size 或 max_connections 都是隔靴搔痒。最容易被忽略的是:你以为在优化“PHP”,其实瓶颈早就在 MySQL 的锁等待队列里排了两百个请求。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











