問題背景
傳統樹狀論壇(如 BBS)在顯示巢狀回覆時,常採用遞歸查詢整張資料表,隨著資料量增長,效能急劇下降。本文提出一種「先取根主題,再以 mysql_data_seek 在單一結果集中跳躍游標」的策略,大幅減少資料庫往返次數,提升顯示速度。
資料表設計
newestbbs(最新主題索引表)
| 欄位 | 說明 |
| id | 根主題 ID |
| time | 最後回覆時間(無回覆則為發文時間) |
bbs(完整文章表)
| 欄位 | 說明 |
| id | 文章本身 ID |
| fathered | 父文章 ID |
| rootid | 根主題 ID |
| time | 發文時間 |
| ... | 其他欄位 |
優化邏輯
- 從
newestbbs 取得最新 20 筆根主題 ID(分頁)。
- 對每個根主題 ID,從
bbs 撈取所有 rootid 相符的文章,按時間排序,一次性取回完整結果集 $res。
- 利用
mysql_data_seek($res, $row) 移動記錄指標,避免重複查詢。
核心程式碼
<?php
// 取得最新主題列表
$listRes = mysql_query("SELECT id FROM newestbbs ORDER BY time LIMIT $offset, 20");
while ($topic = mysql_fetch_assoc($listRes)) {
$rootId = $topic['id'];
// 一次撈取該主題所有回覆
$res = mysql_query("SELECT * FROM bbs WHERE rootid=$rootId ORDER BY time");
// 顯示根主題
$root = mysql_fetch_assoc($res);
displayTopic($root);
// 遞歸顯示回覆,傳入結果集資源
displayReplies($root['id'], $res);
}
function displayReplies($parentId, $resultResource) {
$total = mysql_num_rows($resultResource);
for ($i = 0; $i < $total; $i++) {
mysql_data_seek($resultResource, $i);
$row = mysql_fetch_assoc($resultResource);
if ($row['fathered'] == $parentId) {
displayTopic($row);
displayReplies($row['id'], $resultResource); // 遞歸
}
}
}
?>
關鍵函數:mysql_data_seek
bool mysql_data_seek(resource $result, int $row_number) 允許將結果集的內部指標移動到指定列號(0 起算)。這意味著我們可以在同一結果集中反覆跳轉,模擬多次查詢的效果,卻只需一次資料庫連線。
效能比較
| 指標 | 傳統遞歸查詢 | 本法(單查詢 + 游標跳躍) |
| 資料庫往返次數 | O(N) 次(N 為節點數) | 1 次(每主題) |
| 記憶體用量 | 低 | 中(需載入整主題結果集) |
| 適用規模 | 小型論壇 | 中大型、高頻讀取場景 |
注意事項
- 結果集不宜過大,建議單一主題回覆數在數千筆以內;超大主題仍建議分頁或改用巢狀集合模型。
- 本範例使用已棄用的
mysql_* 函數,實務上請改用 mysqli 或 PDO,邏輯不變。