You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL 8中Laravel QueryBuilder与原生查询性能差异排查

为什么Laravel QueryBuilder生成的查询在MySQL 8下比原生SQL慢很多?

核心原因:SQL结构差异导致MySQL 8优化器选择了低效索引

你遇到的问题本质是Laravel QueryBuilder生成的SQL语句结构,和手写原生SQL存在关键差异,使得MySQL 8的查询优化器放弃了高效的station_id复合索引,转而选择了扫描行数更多的主键索引。具体拆解如下:

1. 两种SQL的结构差异(基于现象推测)

假设你的高效原生SQL是这种利用子查询+覆盖索引的写法:

SELECT r.*
FROM raw_records r
INNER JOIN (
    SELECT station_id, MAX(created_at) AS latest_time
    FROM raw_records
    WHERE station_id IN (/* 目标站点ID列表 */)
    GROUP BY station_id
) sub ON r.station_id = sub.station_id AND r.created_at = sub.latest_time;

这种写法的子查询仅需station_id和created_at字段,若你建了(station_id, created_at)复合索引,MySQL会直接用该索引完成分组与聚合(对应EXPLAIN里的Using index for group-by),无需回表扫描全量数据,因此效率稳定且极高。

而Laravel QueryBuilder默认生成的查询,大概率是直接在主查询中同时执行GROUP BY和ORDER BY的写法:

SELECT *
FROM raw_records
WHERE station_id IN (/* 目标站点ID列表 */)
GROUP BY station_id
ORDER BY created_at DESC;

或是因为使用了latest()等方法,让ORDER BY的优先级被优化器优先考虑。MySQL 8的优化器对这种写法的判断逻辑和5.7不同:它可能认为主键索引(聚簇索引)能更快完成排序(若created_at与主键id递增相关),但实际选择主键索引后需要扫描大量行,导致性能随传入ID数量增加急剧下降。

2. MySQL 8优化器的行为变化

MySQL 8.0对查询优化器做了大量调整,比如默认开启更严格的ONLY_FULL_GROUP_BY、新增直方图统计、更新索引选择逻辑等。在5.7中能正常走station_id索引的写法,到8.0中可能因为SQL结构的细微差异,被优化器判定为“走主键索引更优”,但实际结果完全相反。

比如当QueryBuilder生成的SQL包含SELECT *时,优化器可能认为用主键索引(包含所有字段)可以避免回表,但忽略了station_id复合索引作为覆盖索引在分组场景下的效率优势。

解决办法

  1. 对比SQL语句:先用$query->toSql()输出QueryBuilder生成的SQL,和你的原生高效SQL逐行对比,找出结构差异(比如分组位置、是否使用子查询、排序字段顺序等)。
  2. 模仿原生SQL改写QueryBuilder:按照高效原生SQL的结构,用Laravel的子查询关联写法实现,示例代码:
$stationIds = [1, 2, ..., 100];

// 先获取每个站点的最新记录时间
$subQuery = DB::table('raw_records')
    ->select('station_id', DB::raw('MAX(created_at) as latest_time'))
    ->whereIn('station_id', $stationIds)
    ->groupBy('station_id');

// 关联查询获取完整的最新记录
$latestRecords = DB::table('raw_records')
    ->joinSub($subQuery, 'sub', function ($join) {
        $join->on('raw_records.station_id', '=', 'sub.station_id')
             ->on('raw_records.created_at', '=', 'sub.latest_time');
    })
    ->get();
  1. 确保索引最优:检查station_id索引是否为(station_id, created_at)复合索引,这是实现Using index for group-by的关键,能让子查询完全在索引中完成,无需访问表数据。

内容的提问来源于stack exchange,提问作者Stefano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 06:25:17