在電商、CMS 或論壇系統中,分類結構往往不止兩三層。傳統的「遞歸查詢」雖然直觀,但當分類數量增長到數千甚至上萬時,每次請求都要執行大量 SQL 查詢,效能會急劇下降。本文將介紹幾種主流的無限級分類實現方式,並附上可直接運行的資料表結構與範例代碼。
最基本的設計是 class 表,使用 classid 指向父分類:
-- 表的結構 `class` SET NAMES utf8; CREATE TABLE `class` ( `id` INT(10) NOT NULL AUTO_INCREMENT, `name` VARCHAR(250) CHARACTER SET utf8 DEFAULT NULL, `classid` INT(10) DEFAULT NULL, `sort` INT(10) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=11; -- 範例資料 INSERT INTO `class` (`id`, `name`, `classid`, `sort`) VALUES (1, '中國', 0, 1), (2, '廣西', 1, 1), (3, '桂林', 2, 2), (4, '廣東', 1, 2), (5, '北京', 1, 3), (6, '東莞', 4, 10), (7, '南寧', 2, 10), (8, '陽朔', 3, 10), (9, '柳州', 2, 10), (10, '廣州', 4, 10);
優點:代碼簡單,邏輯清晰。
缺點:每層都要查一次資料庫,N+1 問題嚴重。
<?php
function getChildren($pdo, $parentId = 0) {
$stmt = $pdo->prepare("SELECT * FROM `class` WHERE classid = ? ORDER BY sort");
$stmt->execute([$parentId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$row) {
$row['children'] = getChildren($pdo, $row['id']);
}
return $rows;
}
只查一次資料庫,在記憶體中用引用構建樹結構,效能提升數十倍。
<?php
function buildTree($pdo) {
$stmt = $pdo->query("SELECT * FROM `class` ORDER BY sort");
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
$tree = [];
$map = [];
foreach ($items as $item) {
$map[$item['id']] = &$item;
$item['children'] = [];
}
foreach ($items as $item) {
if ($item['classid'] == 0) {
$tree[] = &$item;
} else {
if (isset($map[$item['classid']])) {
$map[$item['classid']]['children'][] = &$item;
}
}
}
return $tree;
}
在資料表中新增 path 欄位(如 0,1,2,3),或建立獨立的 class_closure 表記錄所有祖先後代關係。查詢子樹、移動節點都變成單條 SQL,極大提升讀寫效能。
實際專案中,建議結合 Redis 緩存整棵分類樹,僅在後台更新時重建緩存,讀取端完全走記憶體,效能最優。