SELECT COUNT(*)
FROM a JOIN b ON a.S = b.S
WHERE a.L > '2014-03-30 00:55:00' AND a.L < '2014-03-30 01:00:00';
預期:a 表驅動,走 a.L 索引過濾 + b.S 索引關聯。
實際:**b 表驅動,全表掃描 103 萬行**,執行極慢。
-- 表 a
S VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL
KEY IX_S (S)
-- 表 b
S VARCHAR(64) DEFAULT NULL -- 預設 utf8_general_ci
KEY IX_S (S)
根因:a.S 為 utf8_bin,b.S 為 utf8_general_ci,字元集/校對規則不一致!**
EXPLAIN SELECT COUNT(*)
FROM a STRAIGHT_JOIN b ON a.S = b.S
WHERE a.L > ... AND a.L < ...;
-- a: range, key=IX_L, rows=63
-- b: index, key=IX_S, rows=1038165, Extra=Using where; Using index; Using join buffer
雖順序對了,但 b 表仍走 **index 全掃描**,非 ref 精準查找。
SHOW INDEX FROM b; -- Cardinality: 1038165 (接近唯一)
SHOW TABLE STATUS LIKE 'b'; -- Rows: 1038165
索引選擇性極好,理應走 ref,為何不走?
EXPLAIN SELECT *
FROM a STRAIGHT_JOIN b ON BINARY a.S = b.S
WHERE a.L > ... AND a.L < ...;
-- b: type=ALL, key=NULL, Extra=Range checked for each record (index map: 0x4)
SELECT * 無法用覆蓋索引,**真相大白:b 表完全走不了索引**,之前是覆蓋索引誤導優化器。
SELECT COUNT(*)
FROM b JOIN (
SELECT S FROM a
WHERE a.L > '2014-03-30 00:55:00' AND a.L < '2014-03-30 01:00:00'
) ta ON b.S = ta.S;
衍生表 ta.S 繼承 utf8_bin,JOIN 時隱式轉換發生在**小表**上,b 表索引可用。
SELECT COUNT(*)
FROM a JOIN b ON BINARY a.S = b.S
WHERE a.L > '2014-03-30 00:55:00' AND a.L < '2014-03:30 01:00:00';
BINARY a.S 強制轉為二進位比較(等同 utf8_bin),消除字元集差異,b 表索引直接生效。**理論上比方案一更快**。
utf8_bin 大小寫敏感,utf8_general_ci 不敏感,命名規則 _bin vs _ciSELECT * 驗證更可靠BINARY 或衍生表轉換** 成本最低💡 一句話心法:JOIN 走不了索引先查字元集,BINARY 一加索引即生效。