傳統的遞歸查詢在處理深層級分類時效能低下。左優先樹模型 透過 lft、rgt 左右值編碼,將樹狀結構轉為區間範圍,實現單次查詢獲取完整樹結構,大幅提升讀取效能。本文詳細解析其表結構、查詢顯示、新增、刪除、修改的完整實現邏輯與代碼。
CREATE TABLE IF NOT EXISTS `category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` int(11) NOT NULL COMMENT '1=文章 2=產品 3=下載',
`title` varchar(50) NOT NULL,
`lft` int(11) NOT NULL,
`rgt` int(11) NOT NULL,
`lorder` int(11) NOT NULL COMMENT '排序',
`create_time` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
核心欄位:lft(左值)、rgt(右值)。根節點涵蓋整棵樹的區間,子節點區間嵌套在父節點區間內。
核心邏輯:先取得根節點左右值 → 查詢區間內所有節點按左值排序 → 利用堆疊 $right 追蹤層級關係 → 輸出時依層級縮進。
function display_tree() {
// 1. 取得根節點左右值
$arr_lr = $this->category->where("title = '頂級欄目'")->find();
if (!$arr_lr) return [];
// 2. 區間查詢並按 lft 排序
$arr_tree = $this->category->query(
"SELECT id, type, title, rgt FROM category
WHERE lft >= {$arr_lr['lft']} AND lft <= {$arr_lr['rgt']}
ORDER BY lft"
);
$right = []; // 堆疊:保存祖先節點的右值
$arr_list = []; // 結果集
foreach ($arr_tree as $v) {
// 3. 維護堆疊:彈出已結束的分支
while (count($right) && $right[count($right)-1] < $v['rgt']) {
array_pop($right);
}
// 4. 依堆疊深度決定縮進
$title = $v['title'];
if (count($right)) {
$title = '|- ' . $title;
}
$arr_list[] = [
'id' => $v['id'],
'type' => $v['type'],
'title' => str_repeat(' ', count($right)) . $title,
'name' => $v['title']
];
// 5. 當前節點右值入堆
$right[] = $v['rgt'];
}
return $arr_list;
}
原理:找到父節點左值 myLeft → 所有左/右值大於 myLeft 的節點 +2 → 新節點插入位置為 myLeft+1、myLeft+2。
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
原理:取得節點 @myLeft、@myRight、寬度 @myWidth = @myRight - @myLeft + 1 → 刪除區間內節點 → 後續節點左右值 - @myWidth。
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;
COMMIT;
END
移動子樹邏輯極其複雜(需同時調整兩棵樹的間隙),工程上常採用 「先刪除後插入」 策略:調用上述刪除與新增存儲過程即可完成移動。
| 優點 | 缺點 |
|---|---|
| 查詢極快(單次 SQL 獲取全樹) | 寫入操作需更新大量行,鎖爭用高 |
| 避免遞歸,應用層代碼簡潔 | 移動子樹邏輯複雜,易出錯 |
| 適合「讀多寫少」的分類、選單、組織架構 | 不適合頻繁調整結構的業務 |
建議:搭配存儲過程封裝寫入邏輯,減少應用層複雜度;若寫入頻繁,可考慮 Closure Table(閉包表) 或 遞歸 CTE(MySQL 8.0+) 方案。