無限級分類是電商、CMS 核心功能。本文提供基於遞歸的完整實作:資料表結構、SQL 測試資料、MVC 風格單檔案展示(列表、新增、編輯、刪除、樹狀顯示、下拉選單),並討論遞歸效能瓶頸與優化方向。
CREATE TABLE `class` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(250) CHARACTER SET utf8 DEFAULT NULL,
`classid` int(10) DEFAULT NULL, -- 父級 ID
`sort` int(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
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);
function infinite_arr($margin, $pid) {
global $class_arr;
$prefix = str_repeat(' ', $margin * 4) . '|----';
foreach ($class_arr as $item) {
if ($item[2] == $pid) {
echo "<tr><td>$prefix{$item[1]}</td><td>{$item[3]}</td>";
echo "<td><a href='?action=edit&id={$item[0]}'>修改</a> | ";
echo "<a href='?action=del&id={$item[0]}'>刪除</a></td></tr>";
infinite_arr($margin + 1, $item[0]);
}
}
}
function infinite_select($margin, $pid, $selectedId) {
global $class_arr;
$prefix = str_repeat(' ', $margin * 4) . '|----';
foreach ($class_arr as $item) {
if ($item[2] == $pid) {
$sel = ($item[0] == $selectedId) ? ' selected' : '';
echo "<option value='{$item[0]}'$sel>$prefix{$item[1]}</option>";
infinite_select($margin + 1, $item[0], $selectedId);
}
}
}
<?php
$mysql = new MySQL_Class('localhost', 'root', '');
$mysql->select_db('test');
$class_arr = array();
$sql = "SELECT * FROM `class` ORDER BY `sort` ASC, `id` DESC";
$query = $mysql->query($sql);
while ($row = $mysql->fetch_array($query)) {
$class_arr[] = array($row['id'], $row['name'], $row['classid'], $row['sort']);
}
// 動作分發
switch ($_GET['action']) {
case 'add': // 顯示新增表單(含下拉選父級)
case 'act_add': // 處理新增 POST
case 'edit': // 顯示編輯表單
case 'act_edit':// 處理編輯 POST(防自我為父級)
case 'del': // 處理刪除
case '': default: // 顯示樹狀列表
}
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>無限級分類</title></head><body>
<h2>分類列表</h2>
<a href="?action=add">添加分類</a>
<table><thead><tr><th>分類名稱</th><th>排序</th><th>操作</th></tr></thead><tbody>
<?php infinite_arr(0, 0); ?>
</tbody></table>
</body></html>
| 問題 | 原因 | 優化方案 |
|---|---|---|
| 大量資料時極慢 | 每次遞歸掃描全陣列 O(n²) | 建立 pid => children 索引陣列,O(n) 單次遍歷 |
| 深層遞歸爆棧 | PHP 預設堆疊限制 | 改用迭代法(堆疊/佇列模擬) |
| 頻繁查詢 DB | 每次操作重新 SELECT 全表 | 引入緩存(Redis/Memcached)、資料庫層面支援(Nested Sets / Closure Table) |
lft/rgt 值,單查詢取得整棵子樹,移動節點代價大。WITH RECURSIVE 原生支援,無需額外欄位。遞歸實現無限分類邏輯清晰、代碼簡短,適合中小資料量(< 1000 條)。大型專案建議採用 **Closure Table + 緩存** 架構,資料庫層面解決樹狀查詢效能,應用層僅處理展示邏輯。理解遞歸與資料庫模式的權衡,是架構師必備能力。