上周发现我写的一个项目,某两个API响应时间竟然长达6.5s,为了进行性能优化,我决定从sql语句的执行情况开始检查。

因为使用了Laravel自带的ORM,所以要先看一下生成的sql语句,使用Laravel自带的tinker命令行工具,执行相关代码:

DB::connection()->enableQueryLog();
CourseRepository::whereHas('properties', function ($query) {
$query->where('user_id', 1);
})->with(['properties' => function ($query) {
$query->where('user_id', 1);
}])->get();
DB::getQueryLog();

得到的执行结果如下所示:

[
[
"query" => "select * from `courses` where exists (select * from `course_properti
es` where `courses`.`id` = `course_properties`.`course_id` and `user_id` = ? and `course_properties`.`deleted_at` is null) and `courses`.`deleted_at` is null",
"bindings" => [
1,
],
"time" => 6543.86,
],
]

毫无疑问地,这个sql查询占用了整个响应周期的绝大部分时间。分析sql语句,发现where exists子语句很有问题,对于每一个查询结果,都要进行一次exists与否的检查,所以时间复杂度是n^2。解决这个问题的方案有两个:

  1. 使用编程语言也就是php实现子语句部分的检查功能
  2. 使用where in子语句迭代主键id的方式代替where exists子语句

在这里我选择的是第一个方案,因为我想了一会如何用Laravel自带的ORM组装这个语句,最终放弃了... 此时也就体现出了重量级ORM的弊端:在组装sql语句的时候约束过多,复杂查询的实现方式繁琐。

经过简单的优化,相关接口的执行时间由原先的6500ms缩减到200ms。