常見需求:依據表 B 的資料更新表 A 對應欄位。許多開發者習慣在應用層「讀 B → 循環更新 A」,實則 MySQL 單條 UPDATE ... JOIN 即可完成,效能提升數量級。本文以實際案例示範重構過程。
| 表 | 欄位 | 樣本資料 |
|---|---|---|
| A 表 (table_a) | id (PK), name, age | (1,'n1',0), (2,'n2',0), (3,'n3',0), (4,'n4',0), (5,'n5',0) |
| B 表 (table_b) | id (PK), age | (1,11), (2,21), (3,31), (4,41), (5,51) |
rs = db.query("SELECT id, age FROM table_b")
for row in rs:
idv, age = row
sql = "UPDATE table_a SET age='%s' WHERE id='%s'" % (age, idv)
db.update(sql)
# 5 次查詢 + 5 次更新 = 10 次網路往返
UPDATE table_a a
INNER JOIN table_b b ON a.id = b.id
SET a.age = b.age;
-- 1 次 SQL,原子執行,毫秒級完成
| 維度 | 應用層循環 | 單條 UPDATE JOIN |
|---|---|---|
| 網路往返 | 2N 次 | 1 次 |
| SQL 解析/優化 | N 次 | 1 次 |
| 事務一致性 | 需手動管理 | 隱式原子事務 |
| 鎖持有時間 | 分散、總時間長 | 集中、總時間極短 |
| 程式碼複雜度 | 高(連線、異常、重試) | 極低 |
UPDATE table_a a
JOIN table_b b ON a.id = b.id
SET a.age = b.age, a.updated_at = NOW()
WHERE b.status = 1 AND a.age != b.age; -- 只更新有變化的
UPDATE table_a a
JOIN (
SELECT user_id, COUNT(*) AS cnt FROM logs
WHERE create_time > DATE_SUB(NOW(), INTERVAL 1 DAY)
GROUP BY user_id
) l ON a.id = l.user_id
SET a.daily_log_count = l.cnt;
UPDATE table_a a
LEFT JOIN table_b b ON a.id = b.id
SET a.age = COALESCE(b.age, a.age); -- B 無資料時保持原值
JOIN ON 條件欄位雙方必須有索引(通常是主鍵)。a.age 與 b.age 型別需相容,否則隱式轉換導致索引失效/精度損失。WHERE a.id BETWEEN 1 AND 10000 循環執行,避免長鎖/大事務。SELECT a.id, a.age, b.age FROM table_a a JOIN table_b b ON a.id = b.id 確認關聯正確,再改為 UPDATE。「能用單條 SQL 解決的,就不要寫循環。」 將集合運算思維內化,是從 CRUD 碼農進階為資料庫工程師的分水嶺。