必须同时满足app_debug=true、database.debug=true、log.level包含'sql'、log.type为'file'四条件,sql日志才能落盘;缺一不可,否则即使配置存在也无记录。

当你在ThinkPHP项目中执行数据库操作却看不到SQL语句、查不到慢查询原因、或调试时发现DB异常无迹可寻,说明SQL日志没真正启用——不是配置写了就行,而是必须满足调试开关、日志级别、驱动类型、路径权限四重条件才能落盘。
确认并开启全局SQL日志记录
这一步是基础前提,缺一不可。若APP_DEBUG=false,即使其他配置全对,SQL日志也不会写入文件。
1、打开.env文件,确保【APP_DEBUG=true】;若已部署上线,请勿在此环境启用,改用Db::listen()方式。
2、编辑config/database.php,将'debug' => true设为true,否则框架不捕获任何SQL语句。
3、进入config/log.php,检查'level'数组是否包含'sql':'level' => ['sql', 'info', 'error'];写成'sql,error'字符串或遗漏引号都会失效。
4、确认channels中file驱动的type值为【'File'(首字母大写)】,小写'file'会导致驱动加载失败且无报错。
5、访问一个含数据库查询的接口(如用户列表页),然后去runtime/log/下找当天日期命名的.log文件,里面应有形如[SQL ]SELECT * FROM `user` [RunTime:0.0012s]的记录。
动态启用SQL日志(绕过配置限制)
适用于生产环境临时开启、或log.php被锁定无法修改的场景,比如CI/CD构建后只读部署包。
方法一:入口文件强制初始化
1、打开public/index.php,在require __DIR__.'/../thinkphp/start.php';之前插入以下代码:
\think\Log::init(['type' => 'File', 'path' => RUNTIME_PATH.'log/', 'level' => ['sql']]);
2、确保RUNTIME_PATH.'log/'目录存在且PHP进程有写权限,否则日志写入会静默失败。
3、请求任意数据库接口,检查runtime/log/下是否生成新SQL日志文件。
方法二:运行时调用Db::listen()
1、在控制器方法开头添加监听器:Db::listen(function ($sql, $time, $explain) { Log::info('SQL Executed', ['sql' => $sql, 'time' => $time]); });
2、注意此方式会【禁用默认SQL日志】,所有SQL输出需由你自行记录,否则日志空白。
3、若需获取绑定参数的真实值,监听回调第二个参数是$time,第三个是$explain;真实绑定值需从Db类内部获取,建议配合Log::info()传入上下文。
查看SQL执行计划与性能瓶颈
光看SQL语句不够,还要知道它怎么执行——EXPLAIN能暴露索引缺失、全表扫描、临时表等致命问题。
第一步:在config/database.php中开启sql_explain
'sql_explain' => true
第二步:确保数据库用户有EXPLAIN权限,MySQL 8+默认开启,但部分云数据库需手动授权。
第三步:触发一次查询后,日志中会出现[EXPLAIN]开头的块,内容类似:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE user ALL NULL NULL NULL NULL 12345 Using where
第四步:重点看type列是否为ALL(全表扫描)、rows是否远超预期、Extra是否含Using filesort或Using temporary——这些是性能杀手。
定位未落盘的日志根源
日志调用了Log::info()却没出现在runtime/log/里?90%是因为以下三个硬性条件没同时满足。
① APP_DEBUG必须为true(开发阶段)或false(生产阶段),但二者触发机制不同:开发阶段靠调试模式激活SQL捕获,生产阶段靠shutdown钩子补全致命错误,不能混用。
② config/log.php中channels.file.path指向的目录必须存在且可写,【runtime/log/目录不存在时不会自动创建】,需手动mkdir -p runtime/log && chmod 755 runtime/log。
③ CLI命令中必须手动调用Log::save(),Web请求结束会自动保存,CLI不会——忘记这句,日志永远卡在内存里。
验证是否生效最直接:在控制器里写Log::channel('file')->info('test-'.time()); → 立即执行Log::save(); → 查看runtime/log/下对应时间戳的文件是否有该条记录。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











