無限級分類是權限系統、商品分類、組織架構等場景的常見需求。傳統遞歸查詢效能差、程式碼複雜。本文介紹基於 pri_path 路徑編碼 + CONCAT 排序的非遞歸方案,並示範如何高效查詢某節點的所有子孫權限。
CREATE TABLE `sh_privilege` (
`id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`pri_name` VARCHAR(15) NOT NULL COMMENT '權限名稱',
`parent_id` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '上級 ID',
`pri_level` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '層級 0/1/2...',
`pri_path` VARCHAR(15) NOT NULL DEFAULT '0' COMMENT '路徑編碼 如 0-2-6',
PRIMARY KEY (`id`),
KEY `pri_path` (`pri_path`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='權限表';
關鍵欄位:
parent_id:直接父節點 IDpri_path:從根節點到當前節點的完整路徑,如 0-2-6 表示 根→2→6pri_level:層級深度,供縮排顯示用利用 CONCAT(pri_path, '-', id) 產生可字典序排序的完整路徑,配合 ORDER BY 即可獲得天然的樹形順序,無需遞歸。
SELECT id, pri_name, pri_path, pri_level
FROM sh_privilege
ORDER BY CONCAT(pri_path, '-', id);
原理: 字串比較天然符合樹的前序遍歷順序,0-1 < 0-1-1 < 0-1-2 < 0-2 ...
利用 LIKE 模糊匹配路徑特徵:%-8-% 表示路徑中包含 -8- 片段(即 8 為祖先)。
SELECT id, pri_name, pri_path
FROM sh_privilege
WHERE CONCAT('-', pri_path, '-') LIKE '%-8-%';
優化建議: 對 pri_path 建立索引,並將查詢改寫為 pri_path LIKE '0-8-%' OR pri_path LIKE '0-%-8-%' 以利用索引前綴匹配(視資料分佈而定)。
-- 假設新增節點 parent_id = 6
-- 1. 先查父節點路徑
SELECT pri_path FROM sh_privilege WHERE id = 6; -- 假設回傳 '0-2-6'
-- 2. 新節點 pri_path = CONCAT(父路徑, '-', 新ID)
-- 需在 INSERT 後 UPDATE,或使用觸發器/應用層邏輯維護
| 優點 | 缺點 |
|---|---|
| 查詢極快,無遞歸、無 CTE 依賴 | 路徑長度有限制 (VARCHAR 長度) |
| 排序、祖先/子孫查詢皆高效 | 移動子樹需更新所有後代 pri_path |
| 易理解、易維護、相容所有 MySQL 版本 | 深度過大時路徑字串較長 |
此方案適合「讀多寫少、層級固定、樹規模中等」的業務場景(如權限、選單、地區分類)。若需頻繁移動大型子樹,可考慮 Closure Table (關係表) 方案。