必须用原生SQL的三类场景:一是聚合统计类报表(如带窗口函数的月度同比环比),二是高频批量写入(如单次5000条日志插入),三是跨库关联或特殊语法需求(如调用存储过程、jsonb_path_exists等);ThinkPHP6中需用Db::query()查、Db::execute()改,参数严格绑定,表名字段名须白名单校验。

当PHP应用在报表生成、高并发写入或复杂联表分析时响应变慢,数据库监控显示慢查询集中在ORM生成的SQL上,说明该考虑绕过ORM直接写原生查询了。
识别必须用原生SQL的三类场景
第一类是聚合统计类报表:比如按月汇总销售数据并计算同比环比,ORM很难写出带窗口函数、WITH RECURSIVE或嵌套子查询的高效语句,强行用Eloquent或GORM会生成冗余JOIN和多次COUNT(*),拖慢整个后台任务。
第二类是高频批量写入:单次插入5000条日志记录,ORM逐条调用save()或CreateInBatches仍要经过模型实例化、钩子触发、字段验证三层开销;而原生INSERT INTO ... VALUES (),(),()一条语句就能完成。
第三类是跨库关联或特殊语法需求:比如需要调用MySQL存储过程、使用PostgreSQL的jsonb_path_exists、或者JOIN分库后的物理表——ORM的抽象层根本无法表达这类操作。
立即学习“PHP免费学习笔记(深入)”;
在Laravel中安全嵌入原生查询
方法一:用DB::select()执行只读查询
DB::select("SELECT u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.created_at > ? GROUP BY u.id", [now()->subDays(30)->toDateTimeString()]);
这一步直接返回数组,不走模型解析,避免了Eloquent的属性映射和访问器调用开销。
方法二:用DB::statement()执行写操作
DB::statement("INSERT INTO logs (level, message, context) VALUES (?, ?, ?)", ['error', 'timeout', json_encode(['url' => $url])]);
【注意】参数必须用?占位符,严禁字符串拼接,否则失去预处理防护
方法三:混合模式——只对关键字段绕过ORM
$users = User::select('id', 'name', 'email')->where('status', 1)->get();
这句看似还是Eloquent语法,但显式指定字段后,生成的SQL变成SELECT id,name,email FROM users,不会SELECT *再丢弃不用字段,内存占用直降40%以上。
ThinkPHP6中切换原生查询的实操路径
第一步:确认当前Db类实例支持query方法
默认配置下Db::query()可用,若报错Class 'think\Db' not found,需检查config/database.php中是否启用database组件。
第二步:构造参数化原生语句
$sql = "SELECT u.username, p.title FROM __PREFIX__user u INNER JOIN __PREFIX__post p ON u.id = p.user_id WHERE u.status = :status AND p.created_time > :time";
第三步:绑定参数并执行
$result = Db::query($sql, ['status' => 1, 'time' => date('Y-m-d H:i:s', strtotime('-7 days'))]);
第四步:结果处理不走模型转换
直接foreach($result as $row) { echo $row['username']; },跳过think\Model的toArray()和获取器逻辑。
【关键前提】所有变量必须通过参数绑定传入,不可用sprintf或字符串拼接注入变量



















