ThinkPHP 6+ 中 M() 方法已被彻底移除,原生查询必须使用 Db::table() 或 Db::name();where 条件需用数组或闭包确保安全;链式调用须遵循 SQL 从句顺序;调试应以执行日志为准,而非 fetchSql()。

ThinkPHP 6+ 里 M() 方法彻底没了,Db::table() 是唯一正解
ThinkPHP 6 开始,M() 方法被完全移除,不是弃用,是删了。它依赖的 Model 类底层逻辑已重构,强行找兼容层只会埋雷。现在所有原生查询必须走 Db 类,哪怕你只是查一张表、不建模型。
常见错误现象:Call to undefined function M() 或 Class 'M' not found;有人试图用 new Model() 模拟,结果连表名都自动加了前缀、字段映射错乱。
-
Db::table('user')直接指定表名,不走模型类,无自动前缀、无字段转换,最接近旧M('user') - 如果项目启用了全局表前缀(如
tp_),Db::name('user')会自动拼前缀,而table()不会——这点必须手动确认配置 - 别在
Db::table()后调->find()就以为能返回模型实例:它只返回数组,要对象得自己封装或改用Db::name()->find()+ 定义模型
where 条件写法变了,字符串条件不能直接拼 SQL
旧版 M('user')->where("id > 10 AND status = 1")->select() 这种写法在 Db 链式操作中依然可用,但风险极高:参数没过滤、SQL 注入敞口大,且 ThinkPHP 6 默认开启 PDO 预处理,字符串条件会被当字面量处理,导致语法错误。
正确做法是用数组或表达式参数,让框架帮你绑定:
立即学习“PHP免费学习笔记(深入)”;
- 安全写法:
Db::table('user')->where(['status' => 1, 'id' => ['>', 10]])->select() - 复杂条件用闭包:
Db::table('user')->where(function ($query) { $query->where('id', '>', 10)->whereOr('status', 0); })->select() - 真要拼原始 SQL?用
Raw:Db::table('user')->where('id', 'IN', Db::raw('(SELECT user_id FROM tp_log WHERE type = 1)')),但Db::raw()必须显式调用,不能直接写字符串
join / order / group 等链式方法顺序不能乱
Db 类的链式调用不是“随便链”,执行顺序直接影响 SQL 生成。比如 order() 放在 limit() 后面,有些驱动(尤其是 SQLite)会报错;join() 如果写在 where() 之后,关联条件里的别名可能还没生效。
推荐固定顺序(按 SQL 从句自然顺序):table() → join() → where() → group() → having() → order() → limit()。这不是强制约束,但跳着链容易触发边界 bug。
-
join()第二个参数必须是完整 ON 表达式,不能只写字段名:->join('user_profile', 'user.id = user_profile.user_id'),漏等号或空格会直接报 SQL 错误 -
order()多字段要用数组:->order(['sort' => 'desc', 'id' => 'asc']),传字符串'sort desc, id asc'虽然能跑,但某些数据库驱动会忽略第二个字段 -
limit(10, 20)表示 offset 10、count 20,和旧版M()->limit('10,20')语义一致,但注意参数是数字,不是字符串
调试 SQL 时别只看 ->fetchSql(),得看实际执行日志
Db::table('user')->where('id', 1)->fetchSql(true) 确实能返回 SQL 字符串,但它不经过查询构造器最终渲染,比如没展开 IN 数组、没替换绑定参数占位符,看起来像 WHERE id IN (?),实际执行却是 WHERE id IN (1,2,3)。
真正要定位问题,得开日志:
- 配置
'log' => ['level' => ['sql']],SQL 语句会输出到 runtime/log/ 目录下,带完整参数值 - 开发环境可临时加
Db::listen(function ($sql, $time, $explain) { dump($sql); });,但注意它捕获的是最终执行语句,含真实参数 - 如果发现 SQL 明明写了
group()却没生效,大概率是select()前漏了field()—— ThinkPHP 6 默认只查*,而某些数据库对GROUP BY严格要求 select 字段必须在 group 中出现
最常被忽略的一点:Db 类默认不开启事务,所有操作都是自动提交。如果原代码靠 M() 的多次调用隐式依赖连接状态,换成 Db 后得显式用 Db::transaction() 包裹,否则并发下数据会错乱。



















