首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP + MySQL 無限級分類樹實現全解析:遞歸、引用構建、JSON 輸出

     編輯:PHP 綜合知識

前言

無限級分類(無限極分類)是電商、CMS、權限系統的核心模型。本文深度解析三種主流實現:遞歸查詢、PHP 端引用構建、路徑枚舉法,並提供完整代碼與效能對比。

數據表設計

CREATE TABLE `category` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(50) NOT NULL COMMENT '分類名',
  `pid` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父ID,0為頂級',
  `path` VARCHAR(255) DEFAULT '' COMMENT '路徑枚舉:0,1,3,',
  `is_dir` TINYINT NOT NULL DEFAULT 1 COMMENT '1目錄 0文件/葉子',
  `sort` INT NOT NULL DEFAULT 0 COMMENT '排序',
  PRIMARY KEY (`id`),
  KEY `idx_pid` (`pid`),
  KEY `idx_path` (`path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

方案一:遞歸查詢(適合數據量小、層級少)

function getTreeRecursive($pid = 0, $level = 0) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM category WHERE pid = ? ORDER BY sort, id");
    $stmt->execute([$pid]);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $tree = [];
    foreach ($rows as $row) {
        $row['level'] = $level;
        $row['children'] = getTreeRecursive($row['id'], $level + 1);
        $tree[] = $row;
    }
    return $tree;
}

// 使用
$tree = getTreeRecursive(0);
echo json_encode($tree, JSON_UNESCAPED_UNICODE);

優點:代碼簡潔、邏輯直觀。
缺點:每層查一次庫,N+1 問題嚴重;深層級易超時、棧溢出。

方案二:一次查詢 + PHP 引用構建(推薦,適合中小數據量)

function buildTree(array $categories) {
    // 1. 以 id 為 key 建立索引,預創建 children
    $tree = [];
    foreach ($categories as $cat) {
        $tree[$cat['id']] = $cat;
        $tree[$cat['id']]['children'] = [];
    }

    // 2. 引用掛掛:將每個節點掛到父節點 children 下
    foreach ($tree as $id => &$node) {
        if ($node['pid'] != 0 && isset($tree[$node['pid']])) {
            $tree[$node['pid']]['children'][] = &$node;
        }
    }
    unset($node); // 解除引用

    // 3. 篩選頂級節點
    $roots = [];
    foreach ($tree as $id => $node) {
        if ($node['pid'] == 0) $roots[] = $node;
    }
    return $roots;
}

// 使用
$stmt = $pdo->query("SELECT * FROM category ORDER BY sort, id");
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
$tree = buildTree($categories);
echo json_encode($tree, JSON_UNESCAPED_UNICODE);

核心技巧:&$node 引用賦值,一次遍歷構建樹,無遞歸,效能極高。

方案三:路徑枚舉法(適合超大數據量、頻繁讀取子樹)

// 寫入時維護 path:父path + 父id + ','
function insertCategory($name, $pid) {
    global $pdo;
    $path = '0,';
    if ($pid != 0) {
        $stmt = $pdo->prepare("SELECT path FROM category WHERE id = ?");
        $stmt->execute([$pid]);
        $parent = $stmt->fetch(PDO::FETCH_ASSOC);
        $path = $parent['path'] . $pid . ',';
    }
    $stmt = $pdo->prepare("INSERT INTO category (name, pid, path) VALUES (?, ?, ?)");
    $stmt->execute([$name, $pid, $path]);
    return $pdo->lastInsertId();
}

// 讀取某節點所有後代(單條 SQL)
function getDescendants($nodeId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM category WHERE path LIKE ? ORDER BY path");
    $stmt->execute(["%,{$nodeId},%"]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

// 讀取某節點祖先鏈
function getAncestors($nodeId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM category WHERE id IN (SELECT CAST(TRIM(BOTH ',' FROM REPLACE(path, ',', ',')) AS UNSIGNED) FROM category WHERE id = ?)");
    // 簡化:應用層解析 path
    $stmt = $pdo->prepare("SELECT path FROM category WHERE id = ?");
    $stmt->execute([$nodeId]);
    $path = $stmt->fetchColumn(); // 如 "0,1,3,"
    $ids = array_filter(explode(',', trim($path, ',')));
    if (!$ids) return [];
    $placeholders = implode(',', array_fill(0, count($ids), '?'));
    $stmt = $pdo->prepare("SELECT * FROM category WHERE id IN ($placeholders) ORDER BY FIELD(id, " . implode(',', $ids) . ")");
    $stmt->execute($ids);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

優點:讀取子樹/祖先單條 SQL,無遞歸,極快。
缺點:寫入/移動節點需更新所有後代 path,事務複雜;path 長度受限(VARCHAR 255 約支援 50 層)。

前端樹形展示(通用遞歸渲染)

function renderTree(array $tree, $level = 0) {
    $html = '
    '; foreach ($tree as $node) { $html .= '
  • '; $html .= str_repeat('—', $level) . htmlspecialchars($node['name']); if (!empty($node['children'])) { $html .= renderTree($node['children'], $level + 1); } $html .= '
  • '; } $html .= '
'; return $html; } echo renderTree($tree);

輸出效果:

電腦
—筆記本
——超級本
——遊戲本
—台式機
手機
—智能機
—功能機

效能對比總結

方案查詢次數PHP 處理適合數據量寫入複雜度推薦指數
遞歸查詢N(層數)遞歸構建< 1,000低★★☆☆☆
一次查詢+引用構建1O(N) 遍歷< 50,000低★★★★★
路徑枚舉1(讀)應用層解析> 100,000高(需更新後代)★★★★☆

進階優化建議

  1. 緩存樹結構:Redis 存儲 JSON,分類變更時失效重建。
  2. 懶加載:前端只請求頂級,展開時 AJAX 請求子級(配合路徑枚舉法極快)。
  3. 物化路徑 + 觸發器:MySQL 觸發器自動維護 path,減輕應用層負擔。
  4. Closure Table:單獨表存儲所有祖先後代關係,查詢靈活、移動子樹高效,適合極複雜層級。

結語

「一次查詢 + 引用構建」是通用最優解,代碼簡潔、效能優異、無遞歸風險。超大數據量或高頻子樹查詢場景,路徑枚舉或 Closure Table 更適合。根據業務規模選型,切忌過度設計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved