跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

Laravel如何排查慢查询_Laravel慢查询日志排查【指南】

慢查询排查需聚焦高频重复SQL而非单次耗时,用DB::listen()全局捕获带参数的真实SQL,写入独立日志并按频次排序优化;验证索引须代入真实参数EXPLAIN,联合索引顺序需匹配WHERE条件,外键与软删除字段需手动建索引,加索引应避免锁表。 慢查询日志本身不等于性能瓶颈,真正卡住页面的常是“单次不慢、但高频重复执行”的 SQL,比如循环里查头像——每条
SELECT * FROM users WHERE id = ?
耗时 15ms,100 次就是 1.5 秒。 DB::listen() 记录真实执行上下文,别只开 enableQueryLog()
DB::enableQueryLog()
只在当前请求生命周期内有效,且不记录绑定参数的实际值,调试时容易误判。而
DB::listen()
是全局监听器,能捕获所有连接的完整上下文。 必须在
AppServiceProvider::boot()
中注册,否则中间件或命令行场景会漏掉 日志要写入独立文件(如
storage/logs/queries.log
),避免和业务日志混在一起难 grep 务必拼接
$query->sql
和
$query->bindings
,用
vsprintf()
替换问号,否则无法在 phpMyAdmin 里准确复现 别设固定阈值(如
time > 1000
),先按频次排序:同一
SELECT * FROM comments WHERE post_id = ?
出现 47 次,比一条耗时 2800ms 的后台统计更值得优先优化 grep 日志定位高频 SQL 时注意预处理语句的变形 日志里看到的
SELECT * FROM posts WHERE user_id = ? AND status = ?
是占位符形式,不代表实际执行逻辑。MySQL 优化器对不同参数组合可能走不同执行计划。 用
grep -n "posts.*user_id" storage/logs/queries.log
提取所有相关行,再人工合并看参数分布(比如
user_id = 123
出现 32 次,
user_id = 999
只有 1 次) 如果某类查询总 rows 数远超表总行数(如
EXPLAIN
显示
rows=124800
,而表只有 5000 行),说明索引失效,不是语句写得慢,而是没走索引 带
ORDER BY created_at DESC LIMIT 20
的分页查询,若
created_at
没单独索引或不在联合索引最右,
Extra
会出现
Using filesort
,这是典型信号 phpMyAdmin 里验证索引必须传真实参数,不能直接跑带问号的 SQL 把日志里抄来的
SELECT * FROM orders WHERE status = ? AND paid_at > ?
直接粘贴进 phpMyAdmin 执行
EXPLAIN
,结果
type=ALL
并不意味着线上也全表扫描——因为 MySQL 没法基于问号做执行计划估算。 从日志中挑出具体一次调用,例如
status = 'paid' AND paid_at > '2026-04-20'
,替换成完整 SQL 再
EXPLAIN SELECT ...
联合索引顺序必须匹配 WHERE 条件顺序:
INDEX(status, paid_at)
有效,
INDEX(paid_at, status)
对该查询无效 软删除字段
deleted_at
如果参与查询(如
where deleted_at is null
),需单独加索引或包含在联合索引中,否则
IS NULL
无法使用 B-tree 索引 外键字段(如
user_id
)Laravel 不自动建索引,
belongsTo
查询必扫全表,必须手动加 大表加索引要绕过锁表,迁移里别直接用 addIndex()
Schema::table('orders', function (Blueprint $table) { $table->index('status'); });
在百万级订单表上执行,MySQL 5.7 默认会锁表,用户请求全部阻塞。 改用原生 SQL +
ALGORITHM=INPLACE
(MySQL 5.6+):
DB::statement("ALTER TABLE orders ADD INDEX idx_orders_status_paid (status, paid_at) ALGORITHM=INPLACE")
加索引前先
SHOW CREATE TABLE orders
确认当前引擎支持在线 DDL(InnoDB 支持,MyISAM 不支持) 关联查询中的隐式条件容易被忽略:比如
withCount('comments')
会生成
SELECT COUNT(*) FROM comments WHERE post_id = ?
,
post_id
必须有索引 高频查询的真实参数分布、索引是否命中、加索引时是否锁表——这三件事串起来才构成完整的慢查询排查闭环。漏掉任何一个,都可能让优化效果打折扣甚至倒退。

相关文章