萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

PHP 實現 MySQL 無限級分類:左優先樹(Nested Set)模型原理與完整代碼

PHP 實現 MySQL 無限級分類:左優先樹模型原理與完整代碼

傳統的遞歸查詢在處理深層級分類時效能低下。左優先樹模型 透過 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+) 方案。

copyright © 萬盛學電腦網 all rights reserved