fetchSql(true) 必须紧邻执行方法(如find())且在with()/cache()等中间方法之后调用,否则无效;getLastSql()仅返回最近一次成功执行的SQL,受全局作用域、多连接、日志开关等影响。

ThinkPHP 6 的 fetchSql 怎么开才真正生效
直接在查询链式调用里加 fetchSql(true) 不一定拿到 SQL——它只对当前查询生效,且必须在执行方法(如 select()、find()、delete())前调用。一旦漏掉或顺序错了,返回的还是结果集而不是 SQL 字符串。
常见错误现象:fetchSql(true)->select() 看起来没问题,但如果中间夹了 with() 或 cache(),部分版本会忽略 fetchSql;更隐蔽的是,事务内或模型事件回调里调用时,可能被自动重置。
-
fetchSql(true)必须紧挨着最终执行方法,比如UserModel::where('id', 1)->fetchSql(true)->find() - 关联查询(
with())下,fetchSql只返回主表 SQL,关联子查询不会一并吐出 - 如果用了
Db::table(),要确保是同一个数据库连接实例,否则fetchSql可能不触发
为什么 getLastSql() 经常返回空或旧语句
getLastSql() 是 Db 类的静态方法,但它只记录「最近一次成功执行的 SQL」,不是「最近一次构建的 SQL」。没执行、执行失败、或者被其他查询覆盖,都会导致它为空或错乱。
典型场景:你在控制器里写了 Db::name('user')->where('id', 1)->find(),然后立刻 Db::getLastSql(),看起来合理——但若模型开启了全局查询范围(baseQuery),或中间件里悄悄执行过日志写入,getLastSql() 就可能返回那条日志 SQL 而不是你的 user 查询。
立即学习“PHP免费学习笔记(深入)”;
-
getLastSql()返回的是已执行语句,fetchSql(true)返回的是待执行语句(未替换参数的原始 SQL) - 多个数据库连接(比如读写分离)下,
getLastSql()默认取主连接,需显式指定连接名:Db::connect('read')->getLastSql() - 调试时建议配合
think-swoole或debugbar使用,避免依赖单次getLastSql()的“即时性”
调试时 SQL 参数没展开?那是预处理占位符没替换
ThinkPHP 默认使用 PDO 预处理,fetchSql(true) 和 getLastSql() 返回的都是带 ? 占位符的 SQL,比如 SELECT * FROM user WHERE id = ?。这不是 bug,是安全机制——真实参数在 PDO 层绑定,不拼进 SQL 字符串。
想看到完整可复制的 SQL(含实际值),不能只靠框架原生方法。手动拼接风险高,但临时调试可以这样绕过:
use think\db\Connection;
$sql = UserModel::where('id', 1)->fetchSql(true)->find();
// $sql 是字符串,但含 ?
$bind = UserModel::getOptions()['where']['id']; // 粗略取绑定值,仅限简单条件
$sql = str_replace('?', '\'' . $bind . '\'', $sql); // ⚠️仅限字符串,数字/NULL 需判断- 更稳妥的做法是开启 PDO 的
ATTR_EMULATE_PREPARES => true(开发环境),让预处理退化为字符串拼接 - TP6.1+ 支持
Db::listen()回调,可在回调里拿到完整 SQL + 绑定参数数组,适合封装调试工具 - 注意:生产环境绝不要关闭预处理,否则 SQL 注入风险直线上升
日志里查不到 SQL?检查数据库配置的 log_sql 开关
TP 的 SQL 日志不是默认全开的。即使你开了 app_debug = true,如果数据库配置里没设 'log_sql' => true,SQL 就不会进日志文件,think-swoole 或 debugbar 也抓不到。
路径:config/database.php 中的连接配置项,不是全局配置。容易踩坑的是:你改了 default 连接的 log_sql,但实际用的是 mysql_read 连接,而后者没配。
- 确认方式:打印
Db::getConfig(),看当前连接的log_sql值是否为true - 日志内容默认写入
runtime/log/,但格式是结构化 JSON,不是纯 SQL;需要搭配日志分析脚本或Debug::dump()才直观 - 高频请求下,
log_sql会显著拖慢性能,调试完务必关掉
最麻烦的其实是嵌套查询和闭包查询——它们可能跨多个模型、触发多次 fetchSql,但只有一条能被 getLastSql() 捕获。这时候得靠日志时间戳+上下文定位,而不是指望某个方法“一键吐出全部”。


















