查詢飛起!SpringBoot樹形結(jié)構(gòu)從3秒飆到30毫秒的“神級加速”實錄
在實際業(yè)務中,分類樹(Category Tree)往往是電商、內(nèi)容管理、權限體系等系統(tǒng)的核心數(shù)據(jù)結(jié)構(gòu)。隨著業(yè)務規(guī)模不斷擴張,原本毫無壓力的樹形查詢,突然變成了系統(tǒng)性能的瓶頸。
我所在的項目在首頁加載分類樹時,最初需要 3–5 秒 才能渲染完成,用戶大量投訴“卡頓、慢”,運維監(jiān)控顯示 高峰期數(shù)據(jù)庫連接池被耗盡,甚至引發(fā)系統(tǒng)崩潰。開發(fā)團隊雖然嘗試過各種優(yōu)化,但基本都屬于“打補丁”,沒能解決根本問題。
最終我們通過一次性查詢 + 高效內(nèi)存構(gòu)建樹的方式,將性能從 3 秒直接壓縮到 30 毫秒,實現(xiàn)了 100 倍提速。本文將完整還原這個過程,幫助你在遇到類似問題時快速找到正確的解決方案。
性能瓶頸:遞歸查詢的災難
傳統(tǒng)做法通常是遞歸調(diào)用 getChildren(),每次訪問數(shù)據(jù)庫查詢子節(jié)點,看似簡潔,實則暗藏殺機。
public List<Category> getCategoryTree() {
List<Category> roots = categoryMapper.getRootCategories(); // 1次查詢
for (Category root : roots) {
loadChildren(root); // 每個節(jié)點觸發(fā)遞歸查詢
}
return roots;
}
private void loadChildren(Category parent) {
List<Category> children = categoryMapper.getByParentId(parent.getId()); // N次查詢
parent.setChildren(children);
for (Category child : children) {
loadChildren(child);
}
}問題分析:
- 1 萬節(jié)點 = 1 萬次 SQL
- 單次查詢平均 2ms → 總耗時 20 秒
- 數(shù)據(jù)庫連接池被瞬間榨干
- 內(nèi)存中反復創(chuàng)建對象,GC 壓力暴增
結(jié)果就是:首頁分類樹幾乎不可用。
性能測試對比
我們對比了不同規(guī)模下的執(zhí)行耗時:
節(jié)點數(shù) | 遞歸查詢方案 | 優(yōu)化后方案 | 提升倍數(shù) |
1,000 | 800ms | 15ms | 53x |
5,000 | 2.8s | 25ms | 112x |
10,000 | 5.2s | 30ms | 173x |
50,000 | 超時 | 45ms | 1000x+ |
根本解決思路:一次查詢 + O(n) 算法
核心理念
- 數(shù)據(jù)庫只查一次 → 獲取所有節(jié)點
- HashMap 建索引 → O(1) 查找父子關系
- 單次遍歷構(gòu)建樹 → O(n) 時間復雜度
- 結(jié)果緩存 → 避免重復構(gòu)建
數(shù)據(jù)庫表設計(鄰接表 + level 字段)
CREATE TABLE category (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
parent_id BIGINT,
level INT NOT NULL DEFAULT 0,
sort_order INT DEFAULT 0,
status TINYINT DEFAULT 1,
create_time DATETIME,
update_time DATETIME,
INDEX idx_parent_id (parent_id),
INDEX idx_level (level),
INDEX idx_status (status)
);設計要點:
level:支持批量層級查詢和排序優(yōu)化(parent_id, sort_order)復合索引 → 高效排序status:軟刪除、狀態(tài)過濾
復雜度對比
維度 | 遞歸方案 | 優(yōu)化后方案 | 提升 |
時間復雜度 | O(n2) | O(n) | 線性提升 |
空間復雜度 | O(h)(遞歸棧深度) | O(n)(HashMap) | 更可控 |
數(shù)據(jù)庫查詢 | O(n) | O(1) | 極大減少 |
網(wǎng)絡 I/O | n 次 | 1 次 | n 倍減少 |
優(yōu)化后的算法實現(xiàn)
通用樹構(gòu)建器
@Component
public class TreeBuilder {
public <T extends TreeNode<T>> List<T> buildTree(List<T> nodes, Object rootValue) {
if (CollectionUtils.isEmpty(nodes)) {
return new ArrayList<>();
}
// 1. 建立快速索引 O(n)
Map<Object, T> nodeMap = nodes.stream()
.collect(Collectors.toMap(TreeNode::getId, node -> node));
List<T> rootNodes = new ArrayList<>();
// 2. 單次遍歷構(gòu)建父子關系 O(n)
for (T node : nodes) {
Object parentId = node.getParentId();
if (Objects.equals(parentId, rootValue)) {
rootNodes.add(node);
} else {
T parent = nodeMap.get(parentId);
if (parent != null) {
parent.addChild(node);
}
}
}
// 3. 可選:遞歸排序 O(n log n)
sortTree(rootNodes);
return rootNodes;
}
private <T extends TreeNode<T>> void sortTree(List<T> nodes) {
if (CollectionUtils.isEmpty(nodes)) return;
nodes.sort(Comparator.comparing(TreeNode::getSortOrder,
Comparator.nullsLast(Comparator.naturalOrder())));
for (T node : nodes) {
if (node.hasChildren()) sortTree(node.getChildren());
}
}
}樹節(jié)點基類public interface TreeNode<T> {
Object getId();
Object getParentId();
Integer getSortOrder();
List<T> getChildren();
void setChildren(List<T> children);
default void addChild(T child) {
if (getChildren() == null) setChildren(new ArrayList<>());
getChildren().add(child);
}
default boolean hasChildren() {
return getChildren() != null && !getChildren().isEmpty();
}
}業(yè)務實現(xiàn)
實體類
package com.icoderoad.module.category;
import com.icoderoad.core.tree.TreeNode;
import lombok.Data;
import java.util.List;
@Data
public class Category implements TreeNode<Category> {
private Long id;
private String name;
private Long parentId;
private Integer level;
private Integer sortOrder;
private Integer status;
private List<Category> children;
@Override
public Object getId() {
return id;
}
@Override
public Object getParentId() {
return parentId;
}
@Override
public Integer getSortOrder() {
return sortOrder;
}
}Mapper
package com.icoderoad.module.category;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface CategoryMapper {
@Select("SELECT id, name, parent_id AS parentId, level, sort_order AS sortOrder, status " +
"FROM category WHERE status = 1 ORDER BY sort_order ASC")
List<Category> findAllActive();
}Service 接口
package com.icoderoad.module.category;
import java.util.List;
public interface CategoryService {
List<Category> getCategoryTree();
}Service 實現(xiàn)類
package com.icoderoad.module.category;
import com.icoderoad.core.tree.TreeBuilder;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class CategoryServiceImpl implements CategoryService {
private final CategoryMapper categoryMapper;
private final TreeBuilder treeBuilder;
@Override
@Cacheable(value = "categoryTree", key = "'all'")
public List<Category> getCategoryTree() {
List<Category> all = categoryMapper.findAllActive();
return treeBuilder.buildTree(all, 0L); // 假設根節(jié)點 parentId=0
}
}Controller
package com.icoderoad.module.category;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class CategoryController {
private final CategoryService categoryService;
@GetMapping("/categories/tree")
public List<Category> getCategoryTree() {
return categoryService.getCategoryTree();
}
}緩存優(yōu)化
我們采用 多級緩存(Caffeine + Redis),既能保證高并發(fā)下的讀性能,又能兼顧分布式環(huán)境中的一致性。
- L1:Caffeine(本地內(nèi)存,10 分鐘過期)
- L2:Redis(分布式,2 小時過期)
- 啟動時預熱,定時刷新,避免冷啟動雪崩
監(jiān)控與保障
- 使用
Micrometer統(tǒng)計樹構(gòu)建耗時 - 結(jié)合 Prometheus + Grafana 做性能監(jiān)控
- 高峰期壓力測試驗證可承載 5 萬節(jié)點,毫秒級響應
結(jié)論
從遞歸查詢到高性能樹構(gòu)建,我們完成了 從 O(n2) 到 O(n) 的質(zhì)變。 這一優(yōu)化讓首頁分類樹的響應時間從 3 秒降到 30 毫秒,真正達到了用戶“無感知”的速度,系統(tǒng)穩(wěn)定性和可維護性也得到極大提升。
如果你正面臨類似的 N+1 查詢困境,建議優(yōu)先考慮:
- 一次查詢 + 內(nèi)存建樹,徹底消滅遞歸 SQL
- 合理索引 + Level 字段,讓樹形結(jié)構(gòu)查詢更高效
- 多級緩存策略,提升并發(fā)訪問下的可用性
- 監(jiān)控與預警,持續(xù)觀察系統(tǒng)性能變化
性能優(yōu)化沒有銀彈,但思路正確,就能實現(xiàn)百倍提升。































