無限級分類是電商、CMS 等系統的核心需求。本文系統梳理遞歸法、路徑枚舉法、修改前序遍歷樹三大主流方案,從數據結構、查詢性能、寫入維護三個維度對比優劣,並給出選型建議。
CREATE TABLE category (
id INT PRIMARY KEY AUTO_INCREMENT,
fid INT NOT NULL COMMENT '父級ID',
title VARCHAR(50)
);
CREATE TABLE category (
id INT PRIMARY KEY AUTO_INCREMENT,
path VARCHAR(255) COMMENT '如 1,3,6 表示所有祖先ID',
title VARCHAR(50)
);
LIKE '1,3%' 快速獲取子樹path,寫入開銷大;不符合第一範式為每個節點分配 lft(左值)和 rgt(右值),通過區間包含關係表示樹結構:
lft < 目標.lft AND rgt > 目標.rgtlft > 目標.lft AND rgt < 目標.rgtlft BETWEEN 目標.lft AND 目標.rgtCREATE TABLE category (
id INT PRIMARY KEY AUTO_INCREMENT,
type INT COMMENT '1文章 2產品 3下載',
title VARCHAR(50),
lft INT NOT NULL,
rgt INT NOT NULL,
lorder INT COMMENT '同級排序',
create_time INT
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SELECT id, type, title, rgt
FROM category
WHERE lft >= :root_lft AND lft <= :root_rgt
ORDER BY lft;
配合 PHP 陣列堆疊邏輯,單次查詢即可構建帶縮進的完整分類樹,無需遞歸。
CREATE PROCEDURE category_insert_by_parent(
IN pid INT, IN title VARCHAR(20), IN type INT,
IN l_order INT, IN pubtime INT
)
BEGIN
DECLARE myLeft INT;
SELECT lft INTO myLeft FROM category WHERE id = pid;
UPDATE category SET rgt = rgt + 2 WHERE rgt > myLeft;
UPDATE category SET lft = lft + 2 WHERE lft > myLeft;
INSERT INTO category(type, title, lft, rgt, lorder, create_time)
VALUES(type, title, myLeft + 1, myLeft + 2, l_order, pubtime);
COMMIT;
END
CREATE PROCEDURE category_delete_by_key(IN id INT)
BEGIN
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM category WHERE id = id;
DELETE FROM category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE category SET lft = lft - @myWidth WHERE lft > @myRight;
END
採用「先刪除後插入」策略,復用上述兩個存儲過程,邏輯清晰且不易出錯。
| 維度 | 遞歸法 | 路徑枚舉 | 前序遍歷樹 |
|---|---|---|---|
| 查詢全樹 | 極差(N+1 查詢) | 良好(單查詢+排序) | 優秀(單查詢+堆疊) |
| 查詢子樹 | 差(遞歸) | 優秀(LIKE 前綴) | 優秀(區間查詢) |
| 查詢祖先路徑 | 差(遞歸) | 優秀(解析 path) | 良好(反向區間) |
| 插入/移動節點 | 極簡單 | 複雜(批量更新 path) | 中等(存儲過程) |
| 刪除節點 | 極簡單 | 中等 | 中等(存儲過程) |
| 存儲空間 | 最小 | 中等 | 中等(雙值) |
實踐經驗:分類業務通常「讀多寫少」,修改前序遍歷樹雖然寫入較繁瑣,但通過存儲過程封裝後,維護成本可控,查詢性能優勢壓倒性,是大多數中大型項目的最優選擇。