TP5.1不直接导致CPU飙升,根本原因在MySQL层,但其SQL构建、事务管理及连接使用会加剧问题;需先通过top确认mysqld进程CPU占比,再结合慢日志、EXPLAIN和tp5.1的Db::getLastSql()定位慢SQL与索引缺陷。

tp5.1 本身不直接控制数据库 CPU,CPU 飙升是 MySQL(或其它后端 DB)层的问题;但 tp5.1 的 SQL 构建方式、查询习惯、事务边界和连接管理,会显著放大或掩盖底层问题。排查必须从 DB 层切入,再回溯到框架调用链。
确认是不是 MySQL 进程真正在吃 CPU
别急着改代码——先看系统层面谁在扛压。
执行 top -c,按 P 排序,观察 mysqld 进程的 %CPU 占比。如果 us(用户态)高而 mysqld 排名靠前,才说明是数据库自身负载过高;如果高的是 php-fpm 或 nginx,那问题可能出在 PHP 层阻塞、循环渲染或未释放 DB 连接。
- 若
mysqld占用不高,检查tp5.1是否存在长事务未提交(比如Db::startTrans()后没commit或rollback),导致连接长期持锁 - 若
mysqld占用高,但show processlist中大量线程状态为Sleep,重点查wait_timeout和应用端连接池配置,避免连接堆积 - 注意:云数据库(如阿里云 RDS、腾讯云 CDB)的监控里“CPU 使用率”是实例维度统计,不代表单个
mysqld进程,需结合SHOW PROCESSLIST和慢日志交叉验证
从 tp5.1 日志反推慢 SQL 源头
tp5.1 默认不记录完整 SQL,需手动开启调试模式并捕获查询上下文。
在 config/database.php 中确保:
'trace' => true,
'log_sql' => true,
'log_file' => 'runtime/log/sql_' . date('Y-m-d') . '.log',
但这只记录执行过的 SQL,不带执行时间。更可靠的方式是配合 MySQL 慢日志 + tp5.1 的 Db::getLastSql() 在关键业务逻辑中打点,例如:
$result = Db::name('order')->where('user_id', $uid)->where('status', 1)->select();
if (Db::getRealSql()) {
\think\Log::record('Slow candidate: ' . Db::getRealSql(), 'sql');
}
- 不要依赖
think-sql-log类插件——它们通常拦截不到预编译参数绑定后的最终语句 -
tp5.1的with()关联查询容易触发 N+1,生成大量单条主键查询,这类 SQL 在慢日志里可能单条不慢,但总量压垮 CPU;用Db::getLastSql()抓取关联生成的全部 SQL 再聚合分析 - 注意
paginate()底层会先执行COUNT(*),若表无主键或 WHERE 条件无法走索引,这个 COUNT 就是全表扫描元凶
EXPLAIN 看懂 tp5.1 生成的 SQL 到底卡在哪
拿到慢 SQL 后,别直接加索引——先 EXPLAIN,尤其盯紧 type、key、rows 和 Extra 字段。
常见陷阱:
-
type是ALL或index→ 基本等于全表/全索引扫描,优先检查 WHERE 条件字段是否缺失索引,或用了函数/隐式转换(如WHERE FROM_UNIXTIME(create_time) = '2026-07-01') -
key为空,但possible_keys有值 → 索引选择失败,可能是统计信息过期(ANALYZE TABLE 表名)或数据倾斜严重 -
rows显示扫描行数远大于结果集 → 即使走了索引,也可能因索引区分度低(如性别字段)或范围查询过大,考虑覆盖索引或条件收窄 -
Extra出现Using filesort或Using temporary→ ORDER BY 或 GROUP BY 未命中索引排序,需创建包含排序字段的联合索引
特别提醒:tp5.1 的 order() 若传入字符串如 order('field(id,3,1,2)'),EXPLAIN 无法识别,会强制 Using filesort;改用原生查询或预计算字段。
索引不是越多越好,tp5.1 场景下要防“假优化”
给每个 WHERE 字段都加单列索引?大概率让优化器更困惑,还拖慢写入。
真正有效的做法:
- 优先构建「高频查询条件 + 排序字段」的联合索引,顺序按
WHERE =、WHERE IN、ORDER BY、SELECT字段排列,例如:WHERE status=1 AND user_id=? ORDER BY create_time DESC→ 建idx_status_user_ctime(status,user_id,create_time) -
tp5.1的whereTime()方法生成的BETWEEN查询,对时间字段单独建索引效果有限,务必和前置等值条件组成联合索引 - 删除长期不用的索引:
SELECT * FROM sys.schema_index_statistics WHERE table_schema = 'your_db' ORDER BY drops DESC LIMIT 10;(MySQL 8.0+);tp5.1的迁移文件里若留着废弃模型的索引定义,上线后容易被遗忘 - 注意 B+ 树索引对
LIKE '%xxx'无效,tp5.1的where('name','like','%关键词%')必须配合全文索引或 ES
最易被忽略的一点:索引能加速查询,但无法解决连接数爆炸。如果 tp5.1 在循环里反复 Db::connect() 而不复用连接,或未配置 pool_size,即便 SQL 全部走索引,连接建立/销毁开销也会把 CPU 拉满。


















