Skip to content

優化 Laravel 中大型 whereIn 查詢緩慢問題 #

Image

Laravel Eloquent whereIn() 每個人都用過,但你曉得但 whereIn() 量大時,可能會造成查詢緩慢問題之一嗎? 他不一定是資料 index 索引的問題,可能是更底層問題喔 這邊提供一個 Laravel 資料庫查詢效能優化的手式

Laravel 查詢緩慢的情境 #

常有的需求是使用指定的 id 取出多個使用者 (e.g. my_table.users.id)

一般透過 eloquent 可能會這樣做 (這邊先不考慮分頁問題)

User::whereIn('id', [1,2,3,4....... x100])->get()

而實際上 Eloquent 會轉換為 SQL statement,如下~ 可以看到 SQL Binding Parameter 的 ?

SELECT * FROM users WHERE id in (?, ?, ?, ?, ?, ......)

但查詢對象達到一定數量之後,會發現資料庫回應速度變慢!

查詢緩慢的問題原因 #

這是因為 Eloquent QueryBuilder 在綁定查詢參數時造成的,他不是 Laravel 的問題而是 PHP Data Objects (PDO) 一個古老的 Bug,PDO 在進行參數綁定時緩慢造成的 (可以想象他要依序綁上上百萬的參數...)

Laravel. 5.7 之後可以改用 whereIntegerInRaw 替換 whereIn() 就如字面上的意思 (raw) 會直接將參數置入 sql 內,省去 PDO bindings 上參數的動作

替換後的查詢語法為:

User::whereIntegerInRaw('id', [1,2,3,4....... x100])->get()

實際的 SQL statement,如下~ 直接就能看到 RAW SQL

SELECT * FROM users WHERE id in (1, 2, 3, 4, 5, ......)

實際上測試如下 #

簡單重現,使用 range(1, 50000) 執行 whereIn(),約耗時 11x ms

$avg = [];
for ($i = 0; $i < 10; $i++) {
    $s = now();
    $c =User::query()->whereIn('id', $list)->count();
    $avg[] =  $s->diffInMilliseconds(now());
}
$r = array_sum($avg)/count($avg);
Image

改用 whereIntegerInRaw() 進行查詢,可以看見大幅降低,耗時約 4x ms

$avg = [];
for ($i = 0; $i < 10; $i++) {
    $s = now();
    $c = User::query()->whereIntegerInRaw('id', $list)->count();
    $avg[] = $s->diffInMilliseconds(now());
}
array_sum($avg)/count($avg);
Image

使用限制 #

當然這麼好的功能當然有些限制要注意

  • 參數只能是 integers

  • 要注意 SQL injection

Laravel Source Code #

Image

可以看到將透過 addBinding() 會將目標對象的 Key 逐一綁進 SQL Query 的語句中~

Image