如果你有寫過程式,處理大量資料時,你可能會習慣用「迴圈」把資料一筆一筆拿出來處理。
但資料庫不是這樣運作的。
這篇文章會帶你理解資料庫的運算方式,以及為什麼這會影響到你寫 SQL 的效能。
寫程式 vs 寫 SQL:資料處理方式大不同
程式設計:用迴圈一筆一筆處理
寫程式的時候,如果要處理一堆資料,你會怎麼做?
通常是用迴圈:
for (let i = 0; i < data.length; i++) {
// 處理第 i 筆資料
}在迴圈裡面,你可以很靈活地控制流程:
- 針對某一筆資料做特別處理:例如第 5 筆資料要用不同的計算方式
- 跑到一半用
break停下來:例如找到目標資料後,就不用繼續跑了 - 用
continue跳過某幾筆:例如遇到空值就跳過,不處理
但資料庫不是這樣運作的。
資料庫:整批處理,沒辦法停下來
資料庫在處理資料時,是以「欄位」為單位,整批整批處理。
它不會一筆一筆跑,也沒有 break 可以讓你中途停下來。
舉個例子:
SELECT id, 名稱, 單價 * 1.05 AS 含稅價
FROM 商品
WHERE 名稱 = '超強資料庫課程'這個查詢做了三件事,我們來看資料庫是怎麼執行的。
FROM:把整張表單拿出來
FROM 商品資料庫會把「商品」這張表單的所有資料都拿出來。
不是拿一筆,是整張表單。
WHERE:整批檢查名稱欄位
WHERE 名稱 = '超強資料庫課程'資料庫會檢查「名稱」這個欄位的所有資料,把符合條件的篩選出來。
它不是檢查一筆、處理一筆,而是整個欄位一路掃過去,掃完之後才把符合條件的資料挑出來。
SELECT:整批計算單價
SELECT id, 名稱, 單價 * 1.05 AS 含稅價最後輸出的時候,資料庫會對所有符合條件的資料,整批計算 單價 * 1.05。
不是算一筆、輸出一筆,而是整批算完、整批輸出。
整批處理如何影響 SQL 效能?
因為這直接影響到效能。
先 WHERE 篩選,再 SELECT 運算
回顧一下執行順序:
- FROM(拿資料)
- WHERE(篩選)
- SELECT(運算、輸出)
注意到了嗎?WHERE 在 SELECT 之前執行。
這代表什麼?
資料庫會先篩選,再運算。
舉個例子:
假設商品表單有 1000 筆資料。
SELECT 單價 * 1.05 AS 含稅價
FROM 商品
WHERE 名稱 = '超強資料庫課程'執行過程是這樣的:
- FROM:拿出 1000 筆資料
- WHERE:篩選出名稱是「超強資料庫課程」的資料,剩下 2 筆
- SELECT:對這 2 筆資料計算
單價 * 1.05
因為先篩選,資料量從 1000 筆變成 2 筆。
所以 單價 * 1.05 這個運算只需要做 2 次,而不是 1000 次。
這就是「先篩選,再運算」的好處:減少運算量,提升效能。
WHERE 條件下得好,效能差很多
如果你的篩選條件下得不好,讓大量資料都通過篩選,後面的運算就會很吃效能。
舉個例子:
假設商品表單有 1000 筆資料。
不好的做法:先運算,再篩選
SELECT *
FROM (
SELECT 名稱, 單價 * 1.05 AS 含稅價
FROM 商品
) AS 計算後的商品
WHERE 名稱 = '超強資料庫課程'這個寫法是先對 1000 筆資料都計算 單價 * 1.05,產生一張臨時表單,最後才篩選出 2 筆。
就算最後只需要 2 筆資料,你也已經算了 1000 次,非常浪費。
好的做法:先篩選,再運算
SELECT 單價 * 1.05 AS 含稅價
FROM 商品
WHERE 名稱 = '超強資料庫課程'執行順序:
- FROM:拿出 1000 筆資料
- WHERE:篩選後剩 2 筆
- SELECT:只需要計算 2 次
單價 * 1.05
先用 WHERE 把資料量縮小,再做運算,效率高很多。
寫 SQL 時,心中要有運算量的概念
當你寫 SQL 的時候,要在心中模擬一下:
- 這個查詢會處理多少筆資料?
- 每個步驟會做多少次運算?
單層篩選的運算量
假設你寫了這個查詢:
SELECT 單價 * 1.05 AS 含稅價
FROM 商品
WHERE 名稱 = '超強資料庫課程'資料庫會這樣執行:
| 步驟 | 動作 | 處理筆數 |
|---|---|---|
| 1 | FROM:拿出商品表單 | 1000 筆 |
| 2 | WHERE:篩選名稱 | 1000 筆 → 2 筆 |
| 3 | SELECT:計算含稅價 | 2 筆 |
總運算量大約是 1000 + 2 = 1002 次。
兩層篩選的運算量
如果你用子查詢做兩層篩選:
SELECT *
FROM (
SELECT *
FROM 商品
WHERE 類別 = '課程'
) AS 課程商品
WHERE 名稱 = '超強資料庫課程'資料庫會這樣執行:
| 步驟 | 動作 | 處理筆數 |
|---|---|---|
| 1 | FROM:拿出商品表單 | 1000 筆 |
| 2 | 第一次 WHERE:篩選類別 | 1000 筆 → 500 筆 |
| 3 | 第二次 WHERE:篩選名稱 | 500 筆 → 2 筆 |
| 4 | SELECT:輸出結果 | 2 筆 |
總運算量大約是 1000 + 500 + 2 = 1502 次。
小心 JOIN 讓運算量爆炸
有些情況下,運算量不是相加,而是相乘。
比如說兩張表單合併(JOIN)的時候,如果沒有下好條件:
| 表單 A | 表單 B | 運算量 |
|---|---|---|
| 1000 筆 | 1000 筆 | 1000 × 1000 = 1,000,000 次 |
這種指數級的暴增,會讓你的網站變得很慢。
之後你會學到一個指令叫 EXPLAIN,它可以在執行查詢之前,先告訴你這個查詢會掃描多少筆資料、運算量大概有多複雜。
這對於優化效能非常有幫助,不過這是比較進階的內容,之後再詳細介紹。
小結
這篇文章介紹了資料庫的批次處理觀念:
- 資料庫是整批處理:以欄位為單位,整批篩選、整批運算、整批輸出
- 沒有 break:不能中途停下來,也不能針對某一筆做特別處理
- 先篩選,再運算:WHERE 在 SELECT 之前執行,所以篩選條件很重要
- 心中要有運算量的概念:寫 SQL 時要估算每個步驟會處理多少資料
- 小心運算量暴增:表單合併時,運算量可能是相乘而不是相加
理解這個觀念後,你就能寫出更有效率的 SQL。