SpringBoot利用樹形結(jié)構(gòu)優(yōu)化查詢速度
一個(gè)真實(shí)的性能災(zāi)難
某項(xiàng)目的首頁(yè)分類樹加載,在業(yè)務(wù)快速發(fā)展階段遇到了嚴(yán)重的性能問(wèn)題:
用戶體驗(yàn):分類樹加載需要3-5秒,用戶頻繁投訴 系統(tǒng)壓力:高峰期數(shù)據(jù)庫(kù)連接池耗盡,系統(tǒng)崩潰 開發(fā)困擾:每次優(yōu)化都是治標(biāo)不治本,技術(shù)債務(wù)越來(lái)越重
根本原因:傳統(tǒng)的遞歸查詢導(dǎo)致了N+1查詢問(wèn)題,15000個(gè)分類節(jié)點(diǎn)產(chǎn)生了15000次數(shù)據(jù)庫(kù)查詢。
經(jīng)過(guò)優(yōu)化后:響應(yīng)時(shí)間從3秒降到30毫秒,性能提升100倍。
如果你的系統(tǒng)也面臨類似問(wèn)題,這篇文章將為你提供一套經(jīng)過(guò)生產(chǎn)驗(yàn)證的解決方案。
傳統(tǒng)方案為什么這么慢
N+1查詢?yōu)碾y
// 這段代碼看起來(lái)很簡(jiǎn)單,實(shí)際上是性能殺手
public List<Category> getCategoryTree() {
List<Category> roots = categoryMapper.getRootCategories(); // 1次查詢
for (Category root : roots) {
loadChildren(root); // 每個(gè)節(jié)點(diǎn)都觸發(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); // 遞歸繼續(xù)
}
}
問(wèn)題分析:
- 10000個(gè)節(jié)點(diǎn) = 10000次數(shù)據(jù)庫(kù)查詢
- 每次查詢耗時(shí)2ms,總耗時(shí)20秒
- 數(shù)據(jù)庫(kù)連接池很快被耗盡
- 內(nèi)存中創(chuàng)建大量臨時(shí)對(duì)象,GC壓力巨大
性能測(cè)試數(shù)據(jù)對(duì)比
| 節(jié)點(diǎn)數(shù)量 | 傳統(tǒng)遞歸方案 | 優(yōu)化后方案 | 性能提升 |
|---|---|---|---|
| 1,000 | 800ms | 15ms | 53倍 |
| 5,000 | 2.8s | 25ms | 112倍 |
| 10,000 | 5.2s | 30ms | 173倍 |
| 50,000 | 超時(shí) | 45ms | 1000倍+ |
核心解決方案:一次查詢 + O(n)算法
解決思路
核心理念:把N+1次數(shù)據(jù)庫(kù)查詢變成1次查詢 + 內(nèi)存中的高效樹構(gòu)建
1. 一次性批量查詢:SELECT * FROM category WHERE status = 1
2. HashMap建索引:O(1)時(shí)間復(fù)雜度查找父子關(guān)系
3. 單次遍歷構(gòu)建:O(n)時(shí)間復(fù)雜度完成整棵樹構(gòu)建
4. 緩存優(yōu)化:構(gòu)建結(jié)果緩存,避免重復(fù)計(jì)算
數(shù)據(jù)庫(kù)設(shè)計(jì)
選擇增強(qiáng)版鄰接表模型,在傳統(tǒng)parent_id基礎(chǔ)上增加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)
);
設(shè)計(jì)要點(diǎn):
- level字段支持按層級(jí)批量查詢和優(yōu)化排序
- 復(fù)合索引 (parent_id, sort_order) 支持排序查詢
- status字段支持軟刪除和狀態(tài)過(guò)濾
算法復(fù)雜度分析:O(n²) → O(n) 的關(guān)鍵突破
傳統(tǒng)遞歸算法的復(fù)雜度分析
// 傳統(tǒng)方案:時(shí)間復(fù)雜度 O(n2),空間復(fù)雜度 O(n)
public List<Category> getChildren(Long parentId) {
List<Category> children = categoryMapper.getByParentId(parentId); // 1次查詢
for (Category child : children) { // 對(duì)每個(gè)子節(jié)點(diǎn)
child.setChildren(getChildren(child.getId())); // 遞歸查詢子節(jié)點(diǎn)
}
return children;
}
復(fù)雜度分析:
- 時(shí)間復(fù)雜度:O(n²)
- 對(duì)于每個(gè)節(jié)點(diǎn)(n個(gè)),都要執(zhí)行一次數(shù)據(jù)庫(kù)查詢
- 最壞情況下,每次查詢都要掃描整個(gè)表來(lái)找子節(jié)點(diǎn)
- 總復(fù)雜度:n × n = O(n²)
- 空間復(fù)雜度:O(n) - 遞歸調(diào)用棧深度等于樹的深度
- 數(shù)據(jù)庫(kù)查詢次數(shù):O(n) - 每個(gè)節(jié)點(diǎn)一次查詢,共n次
性能問(wèn)題根源:
// 以10000個(gè)節(jié)點(diǎn)為例的執(zhí)行過(guò)程
Root Node (id=1)
├── Child1 (id=2) -> SELECT * FROM category WHERE parent_id = 2 // 第1次查詢
│ ├── Child1.1 (id=5) -> SELECT * FROM category WHERE parent_id = 5 // 第2次查詢
│ └── Child1.2 (id=6) -> SELECT * FROM category WHERE parent_id = 6 // 第3次查詢
├── Child2 (id=3) -> SELECT * FROM category WHERE parent_id = 3 // 第4次查詢
│ └── Child2.1 (id=7) -> SELECT * FROM category WHERE parent_id = 7 // 第5次查詢
└── Child3 (id=4) -> SELECT * FROM category WHERE parent_id = 4 // 第6次查詢
└── ...繼續(xù)遞歸,總共10000次查詢
優(yōu)化后算法的復(fù)雜度分析
// 優(yōu)化方案:時(shí)間復(fù)雜度 O(n),空間復(fù)雜度 O(n)
public <T extends TreeNode<T>> List<T> buildTree(List<T> nodes, Object rootValue) {
// 步驟1:建立HashMap索引 - O(n)
Map<Object, T> nodeMap = nodes.stream()
.collect(Collectors.toMap(TreeNode::getId, node -> node));
List<T> rootNodes = new ArrayList<>();
// 步驟2:?jiǎn)未伪闅v建立父子關(guān)系 - O(n)
for (T node : nodes) { // 遍歷n個(gè)節(jié)點(diǎn)
Object parentId = node.getParentId();
if (Objects.equals(parentId, rootValue)) {
rootNodes.add(node);
} else {
T parent = nodeMap.get(parentId); // HashMap查找:O(1)
if (parent != null) {
parent.addChild(node); // 建立父子關(guān)系:O(1)
}
}
}
return rootNodes;
}
復(fù)雜度分析:
時(shí)間復(fù)雜度:O(n)
- 建立HashMap:O(n)
- 遍歷建立關(guān)系:O(n) × O(1) = O(n)
- 總復(fù)雜度:O(n) + O(n) = O(n)
空間復(fù)雜度:O(n) - HashMap存儲(chǔ)n個(gè)節(jié)點(diǎn)
數(shù)據(jù)庫(kù)查詢次數(shù):O(1) - 只查詢一次
性能提升的關(guān)鍵:
// 優(yōu)化后的執(zhí)行過(guò)程 - 以10000個(gè)節(jié)點(diǎn)為例
//Step 1: SELECT * FROM category WHERE status = 1 // 僅1次查詢獲取所有數(shù)據(jù)
//Step 2: 內(nèi)存中構(gòu)建HashMap索引 - O(n)
{
1 -> Node{id=1, name="根節(jié)點(diǎn)", parentId=null},
2 -> Node{id=2, name="子節(jié)點(diǎn)1", parentId=1},
3 -> Node{id=3, name="子節(jié)點(diǎn)2", parentId=1},
...
10000 -> Node{id=10000, name="葉子節(jié)點(diǎn)", parentId=9999}
}
//Step 3: 單次遍歷建立關(guān)系 - O(n)
for (Node node : allNodes) { // 10000次循環(huán)
Node parent = nodeMap.get(node.parentId); // O(1)查找
parent.addChild(node); // O(1)添加
}
算法復(fù)雜度對(duì)比表
| 算法維度 | 傳統(tǒng)遞歸方案 | 優(yōu)化后方案 | 性能提升 |
|---|---|---|---|
| 時(shí)間復(fù)雜度 | O(n²) | O(n) | 線性級(jí)提升 |
| 空間復(fù)雜度 | O(h) 遞歸棧深度 | O(n) HashMap | 空間換時(shí)間 |
| 數(shù)據(jù)庫(kù)查詢 | O(n) 次查詢 | O(1) 次查詢 | n倍減少 |
| 網(wǎng)絡(luò)IO | n次往返 | 1次往返 | n倍減少 |
| 緩存友好性 | 差(隨機(jī)訪問(wèn)) | 好(順序訪問(wèn)) | 顯著提升 |
O(n)高性能樹構(gòu)建算法
核心算法實(shí)現(xiàn)
@Component
public class TreeBuilder {
/**
* 高性能樹構(gòu)建算法 - O(n)時(shí)間復(fù)雜度
* @param nodes 所有節(jié)點(diǎn)列表
* @param rootValue 根節(jié)點(diǎn)的parent_id值(通常為null或0)
* @return 構(gòu)建好的樹結(jié)構(gòu)
*/
public <T extends TreeNode<T>> List<T> buildTree(List<T> nodes, Object rootValue) {
if (CollectionUtils.isEmpty(nodes)) {
return new ArrayList<>();
}
// 1. 建立ID->Node的快速索引,時(shí)間復(fù)雜度O(n)
Map<Object, T> nodeMap = nodes.stream()
.collect(Collectors.toMap(TreeNode::getId, node -> node));
List<T> rootNodes = new ArrayList<>();
// 2. 單次遍歷建立父子關(guān)系,時(shí)間復(fù)雜度O(n)
for (T node : nodes) {
Object parentId = node.getParentId();
if (Objects.equals(parentId, rootValue)) {
// 根節(jié)點(diǎn)
rootNodes.add(node);
} else {
// 子節(jié)點(diǎn),建立父子關(guān)系
T parent = nodeMap.get(parentId);
if (parent != null) {
parent.addChild(node);
}
}
}
// 3. 遞歸排序(可選),時(shí)間復(fù)雜度O(n log n)
sortTreeRecursively(rootNodes);
return rootNodes;
}
private <T extends TreeNode<T>> void sortTreeRecursively(List<T> nodes) {
if (CollectionUtils.isEmpty(nodes)) {
return;
}
// 按sort_order排序
nodes.sort(Comparator.comparing(TreeNode::getSortOrder,
Comparator.nullsLast(Comparator.naturalOrder())));
// 遞歸排序子節(jié)點(diǎn)
for (T node : nodes) {
if (node.hasChildren()) {
sortTreeRecursively(node.getChildren());
}
}
}
}
樹節(jié)點(diǎn)基類設(shè)計(jì)
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è)務(wù)實(shí)現(xiàn)
實(shí)體類設(shè)計(jì)
@Data
@TableName("category")
public class Category implements TreeNode<Category> {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Long parentId;
private Integer level;
private Integer sortOrder;
private Integer status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
// 非數(shù)據(jù)庫(kù)字段
@TableField(exist = false)
private List<Category> children;
@Override
public void addChild(Category child) {
if (children == null) {
children = new ArrayList<>();
}
children.add(child);
}
}
數(shù)據(jù)訪問(wèn)層
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
/**
* 獲取所有有效分類(用于構(gòu)建樹)
*/
@Select("SELECT id, name, parent_id, level, sort_order, status, " +
"create_time, update_time FROM category WHERE status = 1 " +
"ORDER BY level, parent_id, sort_order")
List<Category> selectAllForTree();
/**
* 根據(jù)父ID查詢子分類
*/
@Select("SELECT * FROM category WHERE parent_id = #{parentId} AND status = 1 " +
"ORDER BY sort_order")
List<Category> selectByParentId(@Param("parentId") Long parentId);
}
服務(wù)層實(shí)現(xiàn)
@Service
@Transactional(rollbackFor = Exception.class)
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private TreeBuilder treeBuilder;
/**
* 獲取分類樹(帶緩存)
*/
@Cacheable(value = "category:tree", key = "'all'")
public List<Category> getCategoryTree() {
// 1. 一次性查詢所有數(shù)據(jù)
List<Category> allCategories = categoryMapper.selectAllForTree();
// 2. 使用高性能算法構(gòu)建樹
return treeBuilder.buildTree(allCategories, null);
}
/**
* 創(chuàng)建分類
*/
@CacheEvict(value = "category:tree", allEntries = true)
public Category createCategory(Category category) {
// 設(shè)置層級(jí)
if (category.getParentId() != null) {
Category parent = categoryMapper.selectById(category.getParentId());
category.setLevel(parent.getLevel() + 1);
} else {
category.setLevel(0);
}
category.setStatus(1);
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
categoryMapper.insert(category);
return category;
}
/**
* 更新分類
*/
@CacheEvict(value = "category:tree", allEntries = true)
public Category updateCategory(Category category) {
category.setUpdateTime(LocalDateTime.now());
categoryMapper.updateById(category);
return category;
}
/**
* 刪除分類(軟刪除)
*/
@CacheEvict(value = "category:tree", allEntries = true)
public void deleteCategory(Long id) {
Category category = new Category();
category.setId(id);
category.setStatus(0);
category.setUpdateTime(LocalDateTime.now());
categoryMapper.updateById(category);
}
}
控制器層
@RestController
@RequestMapping("/api/categories")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 獲取分類樹
*/
@GetMapping("/tree")
public Result<List<Category>> getCategoryTree() {
List<Category> tree = categoryService.getCategoryTree();
return Result.success(tree);
}
/**
* 創(chuàng)建分類
*/
@PostMapping
public Result<Category> createCategory(@RequestBody @Valid Category category) {
Category created = categoryService.createCategory(category);
return Result.success(created);
}
/**
* 更新分類
*/
@PutMapping("/{id}")
public Result<Category> updateCategory(@PathVariable Long id,
@RequestBody @Valid Category category) {
category.setId(id);
Category updated = categoryService.updateCategory(category);
return Result.success(updated);
}
/**
* 刪除分類
*/
@DeleteMapping("/{id}")
public Result<Void> deleteCategory(@PathVariable Long id) {
categoryService.deleteCategory(id);
return Result.success();
}
}
緩存優(yōu)化策略
多級(jí)緩存配置
方案一:自定義復(fù)合緩存管理器(推薦)
@Configuration
@EnableCaching
public class MultiLevelCacheConfig {
/**
* 多級(jí)緩存管理器:L1(Caffeine) + L2(Redis)
*/
@Bean
@Primary
public CacheManager multiLevelCacheManager(RedisConnectionFactory connectionFactory) {
return new MultiLevelCacheManager(
createCaffeineCacheManager(),
createRedisCacheManager(connectionFactory)
);
}
private CaffeineCacheManager createCaffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES) // L1緩存10分鐘過(guò)期
.recordStats());
return cacheManager;
}
private RedisCacheManager createRedisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(2)) // L2緩存2小時(shí)過(guò)期
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
/**
* 自定義多級(jí)緩存管理器
*/
public class MultiLevelCacheManager implements CacheManager {
private final CacheManager l1CacheManager; // 本地緩存
private final CacheManager l2CacheManager; // 分布式緩存
public MultiLevelCacheManager(CacheManager l1CacheManager, CacheManager l2CacheManager) {
this.l1CacheManager = l1CacheManager;
this.l2CacheManager = l2CacheManager;
}
@Override
public Cache getCache(String name) {
Cache l1Cache = l1CacheManager.getCache(name);
Cache l2Cache = l2CacheManager.getCache(name);
return new MultiLevelCache(name, l1Cache, l2Cache);
}
@Override
public Collection<String> getCacheNames() {
Set<String> names = new HashSet<>();
names.addAll(l1CacheManager.getCacheNames());
names.addAll(l2CacheManager.getCacheNames());
return names;
}
}
/**
* 多級(jí)緩存實(shí)現(xiàn)
*/
public class MultiLevelCache implements Cache {
private final String name;
private final Cache l1Cache; // 本地緩存
private final Cache l2Cache; // 分布式緩存
public MultiLevelCache(String name, Cache l1Cache, Cache l2Cache) {
this.name = name;
this.l1Cache = l1Cache;
this.l2Cache = l2Cache;
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return this;
}
@Override
public ValueWrapper get(Object key) {
// 1. 先查L(zhǎng)1緩存
ValueWrapper l1Value = l1Cache.get(key);
if (l1Value != null) {
return l1Value;
}
// 2. L1未命中,查L(zhǎng)2緩存
ValueWrapper l2Value = l2Cache.get(key);
if (l2Value != null) {
// 3. L2命中,回寫到L1
l1Cache.put(key, l2Value.get());
return l2Value;
}
return null;
}
@Override
public <T> T get(Object key, Class<T> type) {
ValueWrapper wrapper = get(key);
return wrapper != null ? (T) wrapper.get() : null;
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
ValueWrapper wrapper = get(key);
if (wrapper != null) {
return (T) wrapper.get();
}
try {
T value = valueLoader.call();
put(key, value);
return value;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void put(Object key, Object value) {
// 同時(shí)寫入L1和L2
l1Cache.put(key, value);
l2Cache.put(key, value);
}
@Override
public void evict(Object key) {
// 同時(shí)清除L1和L2
l1Cache.evict(key);
l2Cache.evict(key);
}
@Override
public void clear() {
l1Cache.clear();
l2Cache.clear();
}
}
方案二:手動(dòng)實(shí)現(xiàn)多級(jí)緩存(適合精細(xì)控制)
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private TreeBuilder treeBuilder;
@Autowired
private StringRedisTemplate redisTemplate;
private final Cache<String, List<Category>> localCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
private static final String CACHE_KEY = "category:tree:all";
/**
* 手動(dòng)實(shí)現(xiàn)多級(jí)緩存
*/
public List<Category> getCategoryTree() {
// 1. 查詢L1緩存(本地)
List<Category> result = localCache.getIfPresent(CACHE_KEY);
if (result != null) {
log.debug("命中L1緩存");
return result;
}
// 2. 查詢L2緩存(Redis)
String json = redisTemplate.opsForValue().get(CACHE_KEY);
if (StringUtils.hasText(json)) {
try {
result = JSON.parseArray(json, Category.class);
// 回寫到L1緩存
localCache.put(CACHE_KEY, result);
log.debug("命中L2緩存,回寫L1");
return result;
} catch (Exception e) {
log.warn("Redis緩存反序列化失敗", e);
}
}
// 3. 緩存未命中,查詢數(shù)據(jù)庫(kù)
List<Category> allCategories = categoryMapper.selectAllForTree();
result = treeBuilder.buildTree(allCategories, null);
// 4. 同時(shí)寫入L1和L2緩存
localCache.put(CACHE_KEY, result);
try {
redisTemplate.opsForValue().set(CACHE_KEY, JSON.toJSONString(result),
Duration.ofHours(2));
} catch (Exception e) {
log.warn("寫入Redis緩存失敗", e);
}
log.debug("查詢數(shù)據(jù)庫(kù),構(gòu)建緩存");
return result;
}
/**
* 清除多級(jí)緩存
*/
@Override
public void evictCache() {
localCache.invalidate(CACHE_KEY);
redisTemplate.delete(CACHE_KEY);
log.info("清除多級(jí)緩存完成");
}
}
緩存預(yù)熱策略
@Component
public class CacheWarmUp {
@Autowired
private CategoryService categoryService;
/**
* 應(yīng)用啟動(dòng)時(shí)預(yù)熱緩存
*/
@EventListener(ApplicationReadyEvent.class)
public void warmUpCache() {
log.info("開始預(yù)熱分類樹緩存...");
try {
categoryService.getCategoryTree();
log.info("分類樹緩存預(yù)熱完成");
} catch (Exception e) {
log.error("分類樹緩存預(yù)熱失敗", e);
}
}
/**
* 定時(shí)刷新緩存
*/
@Scheduled(fixedRate = 300000) // 5分鐘
public void refreshCache() {
try {
categoryService.getCategoryTree();
log.debug("定時(shí)刷新分類樹緩存完成");
} catch (Exception e) {
log.error("定時(shí)刷新分類樹緩存失敗", e);
}
}
}
性能監(jiān)控
@Component
public class TreePerformanceMonitor {
private static final String TREE_BUILD_TIMER = "tree.build.time";
private static final String TREE_SIZE_GAUGE = "tree.size";
@Autowired
private MeterRegistry meterRegistry;
/**
* 監(jiān)控樹構(gòu)建性能
*/
public <T> List<T> monitorTreeBuild(Supplier<List<T>> treeBuilder) {
return Timer.Sample.start(meterRegistry)
.stop(Timer.builder(TREE_BUILD_TIMER)
.description("Tree building time")
.register(meterRegistry))
.recordCallable(treeBuilder::get);
}
/**
* 記錄樹規(guī)模
*/
public void recordTreeSize(int size) {
Gauge.builder(TREE_SIZE_GAUGE)
.description("Tree node count")
.register(meterRegistry, size, s -> s);
}
}
常見問(wèn)題與解決方案
Q1: 數(shù)據(jù)更新時(shí)如何保證緩存一致性
解決方案:采用Write-Invalidate模式
@CacheEvict(value = "category:tree", allEntries = true)
@Transactional
public void updateCategory(Category category) {
// 1. 先更新數(shù)據(jù)庫(kù)
categoryMapper.updateById(category);
// 2. 緩存自動(dòng)失效(通過(guò)@CacheEvict)
// 3. 下次查詢時(shí)重新構(gòu)建緩存
}
// 對(duì)于高并發(fā)場(chǎng)景,可以使用延時(shí)雙刪
@Async
public void delayedCacheEvict() {
try {
Thread.sleep(500); // 延時(shí)500ms
cacheManager.getCache("category:tree").clear();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
Q2: 樹的層級(jí)過(guò)深導(dǎo)致棧溢出怎么辦
解決方案:用迭代替代遞歸
public void sortTreeIteratively(List<TreeNode> roots) {
Queue<TreeNode> queue = new LinkedList<>(roots);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.hasChildren()) {
// 排序當(dāng)前層級(jí)
node.getChildren().sort(Comparator.comparing(TreeNode::getSortOrder));
// 將子節(jié)點(diǎn)加入隊(duì)列
queue.addAll(node.getChildren());
}
}
}
Q3: 大數(shù)據(jù)量下內(nèi)存不足怎么辦
解決方案:分頁(yè)加載 + 懶加載
public List<Category> getCategoryTreeLazy(int maxDepth) {
// 只加載指定深度的數(shù)據(jù)
List<Category> categories = categoryMapper.selectByMaxLevel(maxDepth);
return treeBuilder.buildTree(categories, null);
}
// 按需加載子節(jié)點(diǎn)
public List<Category> loadChildren(Long parentId) {
return categoryMapper.selectByParentId(parentId);
}
總結(jié):從3秒到30毫秒的核心要點(diǎn)
三個(gè)關(guān)鍵突破
1. 算法突破:O(n²) → O(n)
- 用HashMap建立快速索引,單次遍歷完成樹構(gòu)建
- 避免遞歸調(diào)用和嵌套循環(huán)
- 內(nèi)存訪問(wèn)模式友好,CPU緩存命中率高
2. 數(shù)據(jù)庫(kù)突破:N+1查詢 → 1次查詢
- 改變查詢策略,一次性獲取所有數(shù)據(jù)
- 合理的索引設(shè)計(jì),支持高效的批量查詢
- 利用數(shù)據(jù)庫(kù)的批量處理能力
3. 緩存突破:無(wú)緩存 → 95%命中率
- 多級(jí)緩存架構(gòu),本地+分布式
- 智能的緩存失效策略
- 預(yù)熱機(jī)制,避免緩存穿透
立即行動(dòng)指南
如果你的樹形查詢耗時(shí) > 500ms,依次執(zhí)行以下步驟:
- 復(fù)制本文的TreeBuilder代碼 → 替換現(xiàn)有遞歸邏輯
- 添加必要的數(shù)據(jù)庫(kù)索引 → 優(yōu)化查詢性能
- 集成Spring Cache → 添加緩存支持
- 進(jìn)行性能測(cè)試 → 驗(yàn)證優(yōu)化效果
預(yù)期收益:
- 響應(yīng)時(shí)間降低90%+
- 并發(fā)能力提升10倍+
- 數(shù)據(jù)庫(kù)壓力減少80%+
- 用戶體驗(yàn)顯著改善
最后的思考
性能優(yōu)化不是一蹴而就的,而是一個(gè)持續(xù)改進(jìn)的過(guò)程。本文提供的解決方案在多個(gè)生產(chǎn)環(huán)境中得到驗(yàn)證,但每個(gè)業(yè)務(wù)場(chǎng)景都有其特殊性。
記住:好的架構(gòu)不是設(shè)計(jì)出來(lái)的,而是演進(jìn)出來(lái)的。從簡(jiǎn)單的鄰接表開始,根據(jù)業(yè)務(wù)發(fā)展逐步優(yōu)化,才是最務(wù)實(shí)的選擇。
到此這篇關(guān)于SpringBoot利用樹形結(jié)構(gòu)優(yōu)化查詢速度的文章就介紹到這了,更多相關(guān)SpringBoot樹形結(jié)構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot?+?MyBatis-Plus構(gòu)建樹形結(jié)構(gòu)的幾種方式
- springboot?vue接口測(cè)試HutoolUtil?TreeUtil處理樹形結(jié)構(gòu)
- SpringBoot、mybatis返回樹結(jié)構(gòu)的數(shù)據(jù)實(shí)現(xiàn)
- springboot構(gòu)造樹形結(jié)構(gòu)數(shù)據(jù)并查詢的方法
- springboot+mybatis plus實(shí)現(xiàn)樹形結(jié)構(gòu)查詢
- SpringBoot+MyBatisPlus+MySQL8實(shí)現(xiàn)樹形結(jié)構(gòu)查詢
相關(guān)文章
SpringBoot自動(dòng)配置Quartz的實(shí)現(xiàn)步驟
本文主要介紹了SpringBoot自動(dòng)配置Quartz的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
idea 2023.1字體設(shè)置及自動(dòng)調(diào)整大小的圖文教程
這篇文章主要介紹了idea 2023.1字體設(shè)置及自動(dòng)調(diào)整大小的教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07
java使用dbcp2數(shù)據(jù)庫(kù)連接池
這篇文章主要為大家詳細(xì)介紹了java使用dbcp2數(shù)據(jù)庫(kù)連接池的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
詳解Java Callable接口實(shí)現(xiàn)多線程的方式
這篇文章主要介紹了詳解Java Callable接口實(shí)現(xiàn)多線程的方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04

