在高併發、大數據量場景下,MySQL 多表 LEFT JOIN 常成為性能瓶頸:優化器難以用好索引、臨時表/文件排序頻發、鎖競爭劇增。本文以電商服務化改造為背景,演示將一條三表 LEFT JOIN 拆解為三條單表查詢,並在 Java 中間件層組裝結果的完整思路與代碼邏輯。
SELECT u1.uid, u1.uname, u2.addr, u3.balance
FROM user AS u1
LEFT JOIN userinfo AS u2 ON u1.uid = u2.uid
LEFT JOIN money AS u3 ON u1.uid = u3.uid
WHERE u1.country = 'CN';
SELECT uid, uname FROM user WHERE country = 'CN';
-- 應用層收到結果:[(32, 'Alice'), (34, 'Bob'), (23, 'Carol'), ...]
SELECT uid, addr FROM userinfo WHERE uid IN (32, 34, 23, ...);
SELECT uid, balance FROM money WHERE uid IN (32, 34, 23, ...);
// Pseudocode
List users = userDao.selectByCountry("CN");
Set uids = users.stream().map(User::getUid).collect(toSet());
Map infoMap = userInfoDao.selectByUids(uids)
.stream().collect(toMap(UserInfo::getUid, v -> v));
Map moneyMap = moneyDao.selectByUids(uids)
.stream().collect(toMap(Money::getUid, v -> v));
for (User u : users) {
u.setAddr(infoMap.get(u.getUid())?.getAddr());
u.setBalance(moneyMap.get(u.getUid())?.getBalance());
}
return users;
| 維度 | LEFT JOIN | 拆分單表查詢 |
|---|---|---|
| 索引利用 | 優化器可能選錯驅動表、忽略索引 | 每條 SQL 明確走主鍵/唯一索引,計劃穩定 |
| 臨時表/文件排序 | 高併發易出現 | 無 |
| 鎖持有時間 | 長(掃描多表) | 極短(單表主鍵查詢) |
| 中間件層組裝開銷 | 無 | 微小(內存 Map Join,網絡 RT 增加 1-2 次) |
| 服務化友好度 | 跨庫/跨服務無法 JOIN | 天然適配微服務、分庫分表 |
IN 列表過長(>1000)需分批,或用臨時表 CREATE TEMPORARY TABLE tmp_ids ... 再 JOINMap<Long, List<Detail>>「用空間換時間、用應用層 CPU 換數據庫 CPU」是高併發架構常見權衡。將 LEFT JOIN 下推到應用層組裝,犧牲少量網絡 RT 與代碼複雜度,換取數據庫層穩定的索引查詢計劃、極低鎖競爭、線性擴展能力,是服務化、分庫分表階段的必經之路。