資料庫字符集 `utf8mb4`、校對規則 `utf8mb4_general_ci` (或 `0900_ai_ci`),直接 `ORDER BY name` 依 Unicode 碼點排序,中文順序混亂 (如「劉」U+5283 在「張」U+5F20 之前)。
SELECT * FROM tags
ORDER BY CONVERT(name USING gbk) COLLATE gbk_chinese_ci
LIMIT 100;
| 方案 | 實現層級 | 查詢效能 | 字符覆蓋 | 維護成本 | 推薦等級 |
|---|---|---|---|---|---|
| 應用層預計算 + 索引欄位 | Application | ⭐⭐⭐⭐⭐ 索引範圍掃描 | 完整 (字典可擴展) | 低 (Composer 套件) | ★★★★★ 生產首選 |
| 資料庫虛擬欄位 + 函數 (8.0+) | Database | ⭐⭐⭐ 虛擬欄位索引 | 取決於字典表 | 中 (純 SQL 維護) | ★★★★ 遷移/單體首選 |
| 資料庫觸發器 + 實體欄位 (5.7) | Database | ⭐⭐⭐ 實體索引 | 取決於字典表 | 中 | ★★★ 5.7 環境 |
| CONVERT + COLLATE | Query | ⭐ 全表掃描 | 不完整 (GBK 限制) | 零 | ☆ 僅臨時/小表 |
-- 1. 新增欄位
ALTER TABLE tags ADD COLUMN pinyin_first CHAR(1) CHARSET ascii NOT NULL DEFAULT '#',
ADD COLUMN pinyin_full VARCHAR(500) CHARSET ascii NOT NULL DEFAULT '',
ADD INDEX idx_pinyin_first (pinyin_first),
ADD INDEX idx_pinyin_full (pinyin_full);
-- 2. 分批回填 (假設 500 萬行)
DELIMITER //
CREATE PROCEDURE backfill_pinyin()
BEGIN
DECLARE v_max_id BIGINT DEFAULT (SELECT MAX(id) FROM tags);
DECLARE v_batch_start BIGINT DEFAULT 0;
DECLARE v_batch_size INT DEFAULT 5000;
WHILE v_batch_start <= v_max_id DO
UPDATE tags t
JOIN (
SELECT id,
UPPER(SUBSTRING(pinyin_full(name),1,1)) AS pf,
pinyin_full(name) AS pfull
FROM tags
WHERE id > v_batch_start AND id <= v_batch_start + v_batch_size
) src ON t.id = src.id
SET t.pinyin_first = src.pf, t.pinyin_full = src.pfull;
SET v_batch_start = v_batch_start + v_batch_size;
DO SLEEP(0.1); -- 降低主從延遲、減輕主庫壓力
END WHILE;
END //
DELIMITER ;
CALL backfill_pinyin();
DROP PROCEDURE backfill_pinyin;
-- 1. 確認無 NULL/預設值殘留
SELECT COUNT(*) FROM tags WHERE pinyin_first = '#' OR pinyin_full = '';
-- 2. 抽樣對比排序結果
SELECT name, pinyin_first FROM tags ORDER BY pinyin_first, pinyin_full LIMIT 50;
SELECT name FROM tags ORDER BY CONVERT(name USING gbk) COLLATE gbk_chinese_ci LIMIT 50;
-- 兩結果應一致 (除 GBK 不支援字符)
-- 3. EXPLAIN 確認走索引
EXPLAIN SELECT * FROM tags ORDER BY pinyin_first, pinyin_full LIMIT 100;
-- type=index, key=idx_pinyin_first, Extra=Using index