為優化查詢,開發者常在 VARCHAR 長字段上建立前綴索引(如 INDEX idx_name (col(8)))。但實測發現:雖查詢大幅加速,UPDATE 性能卻急劇下降。本文通過億級數據實測量化影響,剖析底層機制,並提出「垂直拆分+映射表」等優化方案。
| 場景 | 操作 | 耗時 | 說明 |
|---|---|---|---|
| 基線 | 全表 UPDATE 無索引字段 | ~4 小時 | 純數據寫入 |
| 加前綴索引後 | 同樣 UPDATE | ~9 小時 | 索引維護開銷巨大 |
-- 測試 SQL
ALTER TABLE tbl_name ADD INDEX platforms_index (platforms(8));
-- UPDATE 邏輯(Python 伪代碼)
for idx in range(1, count+1):
sql = "UPDATE tbl_name SET platforms='%s' WHERE id=%d" % (timestamp(), idx)
cursor.execute(sql)
-- 原表
CREATE TABLE main_tbl (
id BIGINT PRIMARY KEY,
-- 移除 platforms 字段
other_cols...
);
-- 映射表(去重後極小)
CREATE TABLE platforms_map (
id INT AUTO_INCREMENT PRIMARY KEY,
platform_hash CHAR(8) NOT NULL, -- 前 8 字符 hash
platform_full VARCHAR(255) NOT NULL,
UNIQUE KEY uk_hash (platform_hash)
);
-- 關聯查詢
SELECT m.*, p.platform_full
FROM main_tbl m
JOIN platforms_map p ON m.platform_id = p.id;
優勢:映射表極小(去重後),UPDATE 僅修改映射表單行,主表無索引維護;適用:重複率高、更新頻繁、查詢需精確匹配
-- 若查詢僅需前 8 字符,考慮冗余字段 ALTER TABLE tbl_name ADD COLUMN platforms_prefix CHAR(8) GENERATED ALWAYS AS (LEFT(platforms, 8)) STORED; CREATE INDEX idx_prefix ON tbl_name (platforms_prefix);
通過 SELECT COUNT(DISTINCT LEFT(col, N))/COUNT(*) FROM tbl 計算不同前綴長度的選擇性,選擇「選擇性>0.9 且長度最短」的 N。
| 場景特徵 | 推薦方案 | 核心理由 |
|---|---|---|
| 高重複率、高更新頻、精確查詢 | 垂直拆分+映射表 | 主表零索開銷,映射表極小 |
| 低重複率、高更新頻、模糊查詢 | 移除索引+全文/ES | 避免索引維護,查詢下推專用引擎 |
| 唯一約束業務需求 | 全長度唯一索引 | 數據完整性優先,接受寫入代價 |
| 唯讀/低寫入、高查詢 | 前綴索引可接受 | 讀寫比>100:1 時收益大於成本 |
有興趣的讀者可自行驗證:
1. 建立映射表方案,測試 UPDATE 耗時
2. 對比 INSERT ... ON DUPLICATE KEY UPDATE 映射表的並發性能
3. 測試不同前綴長度的選擇性曲線