如何优化 PostgreSQL 中对于树形结构数据的查询?

news2024/12/26 10:25:14

文章目录

  • 一、数据模型选择
    • (一)邻接表模型
    • (二)路径枚举模型
    • (三)嵌套集模型
  • 二、索引策略
    • (一)对于邻接表模型
    • (二)对于路径枚举模型
    • (三)对于嵌套集模型
  • 三、查询优化
    • (一)邻接表模型的查询优化
    • (二)路径枚举模型的查询优化
    • (三)嵌套集模型的查询优化
  • 四、示例代码及解释
  • 五、扩展和高级技巧
    • (一)物化视图
    • (二)分区表
    • (三)缓存策略
    • (四)数据库参数调整
  • 六、性能测试和监控

美丽的分割线

PostgreSQL


在 PostgreSQL 中,处理树形结构数据的查询是一个常见但具有挑战性的任务。树形结构数据常用于表示层次关系,如组织结构、类目体系等。优化此类查询需要综合考虑数据结构设计、索引使用和查询语句的优化等多个方面。

美丽的分割线

一、数据模型选择

首先,选择合适的数据模型来存储树形结构是优化查询的基础。以下是几种常见的数据模型:

(一)邻接表模型

这种模型通过在表中添加一个指向父节点的引用列来表示树形关系。

CREATE TABLE tree_nodes (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    parent_id INT REFERENCES tree_nodes(id)
);

优点:简单直观,插入和更新操作相对容易。
缺点:查询子树或整个树的操作较复杂,通常需要递归查询。

(二)路径枚举模型

每个节点存储从根节点到自身的完整路径,路径以字符串形式表示。

CREATE TABLE tree_nodes (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    path VARCHAR(255) 
);

优点:查询父节点和祖先节点比较方便。
缺点:更新节点的路径时比较复杂。

(三)嵌套集模型

每个节点存储表示其在树中位置的左值和右值。

CREATE TABLE tree_nodes (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    lft INT,
    rgt INT
);

优点:查询子树和祖先节点效率高。
缺点:插入和删除节点时需要重新计算一些节点的左值和右值。

对于大多数情况,如果树形结构相对稳定,较少进行节点的插入和删除操作,嵌套集模型可能是性能较好的选择。

美丽的分割线

二、索引策略

合适的索引可以显著提高查询性能。

(一)对于邻接表模型

  1. parent_id 列上创建普通索引可以加速查找特定父节点下的子节点。
CREATE INDEX idx_parent_id ON tree_nodes (parent_id);
  1. 对于递归查询,考虑使用函数索引。例如,如果经常根据节点的深度进行查询,可以创建一个计算深度的函数,并在该函数结果上创建索引。
CREATE FUNCTION node_depth(node_id INT) RETURNS INT AS
$$
    WITH RECURSIVE node_path AS (
        SELECT id, parent_id, 0 AS depth
        FROM tree_nodes
        WHERE id = node_id
        UNION ALL
        SELECT tn.id, tn.parent_id, np.depth + 1 AS depth
        FROM tree_nodes tn JOIN node_path np ON tn.id = np.parent_id
    )
    SELECT MAX(depth) FROM node_path;
$$ LANGUAGE SQL;

CREATE INDEX idx_node_depth ON tree_nodes ((node_depth(id)));

(二)对于路径枚举模型

可以在 path 列上创建索引以加速包含父路径的查询。

CREATE INDEX idx_path ON tree_nodes (path);

(三)对于嵌套集模型

lftrgt 列上创建索引通常能提高查询性能。

CREATE INDEX idx_nested_set ON tree_nodes (lft, rgt);

美丽的分割线

三、查询优化

优化查询语句的结构和方法也是至关重要的。

(一)邻接表模型的查询优化

  1. 获取指定节点的子节点:
WITH RECURSIVE sub_tree AS (
    SELECT * FROM tree_nodes WHERE id = 1 -- 起始节点
    UNION
    SELECT tn.* FROM tree_nodes tn JOIN sub_tree st ON tn.parent_id = st.id
)
SELECT * FROM sub_tree;

通过 WITH RECURSIVE 子句进行递归查询,但这种方式在处理大型树形结构时可能性能不佳。可以考虑预先计算和存储子树信息来优化。

  1. 获取指定节点的所有祖先节点:
WITH RECURSIVE ancestor_nodes AS (
    SELECT * FROM tree_nodes WHERE id = 5 -- 目标节点
    UNION
    SELECT tn.* FROM tree_nodes tn JOIN ancestor_nodes an ON tn.id = an.parent_id
)
SELECT * FROM ancestor_nodes;

同样是使用递归查询来获取祖先节点。

(二)路径枚举模型的查询优化

  1. 获取指定节点的子节点:
SELECT * FROM tree_nodes WHERE path LIKE '/1/%'; -- 假设 1 是父节点的 ID

通过字符串匹配来查找子节点,利用索引可以提高性能。

  1. 获取指定节点的祖先节点:
SELECT * FROM tree_nodes WHERE id = SPLIT_PART('/1/2/3', '/', 3); -- 从路径中提取祖先节点的 ID

通过字符串处理函数提取祖先节点的 ID 进行查询。

(三)嵌套集模型的查询优化

  1. 获取指定节点的子树:
SELECT * FROM tree_nodes WHERE lft > 10 AND rgt < 20; -- 假设 10 和 20 是指定节点的左右值

直接使用左右值范围进行查询,效率通常较高。

  1. 获取指定节点的祖先节点:
SELECT * FROM tree_nodes tn
JOIN tree_nodes parent ON tn.lft BETWEEN parent.lft AND parent.rgt
WHERE tn.id = 5; -- 假设 5 是目标节点的 ID

通过连接和范围比较来获取祖先节点。

美丽的分割线

四、示例代码及解释

以下是使用不同数据模型和相应查询优化的示例代码:

首先是邻接表模型:

-- 创建表
CREATE TABLE adjacency_tree (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    parent_id INT REFERENCES adjacency_tree(id)
);

-- 插入示例数据
INSERT INTO adjacency_tree (name, parent_id)
VALUES 
    ('Root', NULL),
    ('Node 1', 1),
    ('Node 2', 1),
    ('Node 1.1', 2),
    ('Node 1.2', 2);

-- 递归查询获取子树
WITH RECURSIVE sub_tree AS (
    SELECT * FROM adjacency_tree WHERE id = 1 
    UNION
    SELECT tn.* FROM adjacency_tree tn JOIN sub_tree st ON tn.parent_id = st.id
)
SELECT * FROM sub_tree;

-- 递归查询获取祖先节点
WITH RECURSIVE ancestor_nodes AS (
    SELECT * FROM adjacency_tree WHERE id = 4 
    UNION
    SELECT tn.* FROM adjacency_tree tn JOIN ancestor_nodes an ON tn.id = an.parent_id
)
SELECT * FROM ancestor_nodes;

对于路径枚举模型:

-- 创建表
CREATE TABLE path_enumeration_tree (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    path VARCHAR(100)
);

-- 插入示例数据
INSERT INTO path_enumeration_tree (name, path)
VALUES 
    ('Root', '/'),
    ('Node 1', '/Root/Node 1'),
    ('Node 2', '/Root/Node 2'),
    ('Node 1.1', '/Root/Node 1/Node 1.1'),
    ('Node 1.2', '/Root/Node 1/Node 1.2');

-- 查询子节点
SELECT * FROM path_enumeration_tree WHERE path LIKE '/Root/Node 1/%';

-- 查询祖先节点
SELECT * FROM path_enumeration_tree WHERE SPLIT_PART(path, '/', 3) = 'Node 1';

最后是嵌套集模型:

-- 创建表
CREATE TABLE nested_set_tree (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    lft INT,
    rgt INT
);

-- 插入示例数据
INSERT INTO nested_set_tree (name, lft, rgt)
VALUES 
    ('Root', 1, 10),
    ('Node 1', 2, 5),
    ('Node 2', 6, 9),
    ('Node 1.1', 3, 4),
    ('Node 1.2', 7, 8);

-- 查询子树
SELECT * FROM nested_set_tree WHERE lft > 2 AND rgt < 9;

-- 查询祖先节点
SELECT * FROM nested_set_tree tn
JOIN nested_set_tree parent ON tn.lft BETWEEN parent.lft AND parent.rgt
WHERE tn.id = 4;

在实际应用中,根据数据的特点、查询的需求和性能要求,综合选择数据模型和优化策略。

美丽的分割线

五、扩展和高级技巧

(一)物化视图

对于频繁使用的复杂树形查询,可以创建物化视图来预计算和存储结果。

CREATE MATERIALIZED VIEW materialized_subtree AS
WITH RECURSIVE sub_tree AS (
    SELECT * FROM tree_nodes WHERE id = 1 
    UNION
    SELECT tn.* FROM tree_nodes tn JOIN sub_tree st ON tn.parent_id = st.id
)
SELECT * FROM sub_tree;

然后可以直接对物化视图进行查询,从而避免重复计算。

(二)分区表

如果树形数据量非常大,可以考虑按某些规则对表进行分区,例如按节点的深度或特定的父节点进行分区。

CREATE TABLE tree_nodes (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    parent_id INT REFERENCES tree_nodes(id),
    depth INT
)
PARTITION BY RANGE (depth);

CREATE TABLE tree_nodes_part1 PARTITION OF tree_nodes
    FOR VALUES FROM (0) TO (5);

CREATE TABLE tree_nodes_part2 PARTITION OF tree_nodes
    FOR VALUES FROM (6) TO (10);

(三)缓存策略

使用数据库的缓存机制,将经常访问的树形结构数据缓存起来,减少磁盘 I/O 操作。

(四)数据库参数调整

根据服务器的硬件资源和工作负载,合理调整 PostgreSQL 的相关参数,如共享缓冲区大小、工作内存等。

美丽的分割线

六、性能测试和监控

在实施优化策略后,进行性能测试和监控是非常重要的。可以使用工具如 pgbench 模拟并发查询负载,观察查询的响应时间、吞吐量等指标,并通过 EXPLAIN 命令分析查询计划,确保优化措施达到预期效果。

例如,对于前面提到的各种查询,可以分别在不同数据量和工作负载下进行测试,比较它们在不同优化策略下的性能表现。


美丽的分割线

🎉相关推荐

  • 🍅关注博主🎗️ 带你畅游技术世界,不错过每一次成长机会!
  • 📢学习做技术博主创收
  • 📚领书:PostgreSQL 入门到精通.pdf
  • 📙PostgreSQL 中文手册
  • 📘PostgreSQL 技术专栏

PostgreSQL

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1905809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于pi控制的数字锁相环simulink建模与仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频&#xff09…

ARM架构以及程序运行解析

文章目录 1. ARM架构 2. ARM处理器程序运行的过程 3. 示例 3. 基于ARM架构的STM32单片机 1. 运行模式 2. 寄存器组 3. STM32的基本结构 4. STM32的运行模式 4. 寄存器组详解 1. 未备份寄存器 2. 备份寄存器 3. 程序计数器 4. 程序状态寄存器 5. CPSR和SPSR寄存器…

【TB作品】51单片机 Proteus仿真 00001仿真实物PID电机调速系统

实验报告&#xff1a;Proteus 仿真 PID 电机调速系统 一、实验背景 PID&#xff08;比例-积分-微分&#xff09;控制器广泛应用于工业控制系统中&#xff0c;用于调节各种物理变量。本实验的目的是通过 Proteus 仿真软件设计并实现一个 PID 电机调速系统&#xff0c;以控制直…

记一次酣畅淋漓的UDF提权(Linux)

外网打点就不放了&#xff0c;翻了一下具备suid权限的命令&#xff0c;没啥结果。 可疑的命令是/usr/lib/dbus-1.0/dbus-daemon-launch-helper但是没有找到用这个命令提权的资料。 弹shell后翻找一下源码&#xff0c;/app/api.py文件中链接了mysql&#xff0c;事出反常必有妖&…

水冷液冷负载系统的六种基本类型

您可以选择六种基本类型的冷却系统&#xff0c;以满足负载的冷却需求。每个人都有其优点和缺点。本文旨在识别不同类型的冷却系统并确定它们的优缺点&#xff0c;以便您可以根据自己的需求做出明智的选择。 液体冷却系统有六种基本类型&#xff1a; 1.液对液 2.闭环干燥系统…

【论文阅读】AsyncDiff: Parallelizing Diffusion Models by Asynchronous Denoising

论文&#xff1a;2406.06911 (arxiv.org) 代码&#xff1a;czg1225/AsyncDiff: Official implementation of "AsyncDiff: Parallelizing Diffusion Models by Asynchronous Denoising" (github.com) 简介 异步去噪并行化扩散模型。提出了一种新的扩散模型分布式加…

J024_打印电影的全部信息

一、需求描述 展示多部电影的信息。 电影信息包括&#xff1a;电影名称、电影得分、电影票价格。 二、代码实现 2.1 Movie类 package com.itheima.collection;public class Movie {//电影名称private String name;//电影得分private int score;//电影票价格private double…

前端面试题(CSS篇五)

一、设备像素、css 像素、设备独立像素、dpr、ppi 之间的区别&#xff1f; 设备像素指的是物理像素&#xff0c;一般手机的分辨率指的就是设备像素&#xff0c;一个设备的设备像素是不可变的。 css像素和设备独立像素是等价的&#xff0c;不管在何种分辨率的设备上&#xff0c;…

LLaMA2模型开源商用:实力比肩ChatGPT,探索AI新高度

【大模型】可商用且更强的 LLaMA2 来了 LLaMA2 简介 论文 GitHubhuggingface模型列表训练数据训练信息模型信息 许可证参考 LLaMA2 简介 2023年7月19日&#xff1a;Meta 发布开源可商用模型 Llama 2。 Llama 2是一个预训练和微调的生成文本模型的集合&#xff0c;其规模从…

java中反射(Reflection)的4个作用

java中反射&#xff08;Reflection&#xff09;的4个作用 作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断任意一个类所具有的成员变量和方法作用4、在运行时调用任意一个对象的方法总结 &#x1f496;The Begin&#x1f496;…

shared_ptr 线程安全

为什么 shared_ptr 可以安全地在多个线程中共享&#xff1f; 循环引用 因为shared_ptr std::shared_ptr 的引用计数是线程安全的。这意味着你可以在多个线程中安全地拷贝、赋值和销毁 std::shared_ptr。然而&#xff0c;访问或修改 shared_ptr 所指向的对象时&#xff0c;需要…

SpringBoot彩蛋之定制启动画面

写在前面 在日常开发中&#xff0c;我们经常会看到各种各样的启动画面。例如以下几种 ① spring项目启动画面 ② mybatisplus启动画面 ③若依项目启动画面 还有很多各式各样好看的启动画面&#xff0c;那么怎么定制这些启动画面呢&#xff1f; 一、小试牛刀 ① 新建一个Spr…

聊聊数据库变更管控的白屏化

在前文中介绍了当涉及到数据库相关的变更如数据更新或者误删表等误操作时&#xff0c;通过延迟库或者闪回等功能来恢复业务&#xff0c;这些已经属于事后的故障处理了。当故障发生后&#xff0c;所要面临的是故障影响的不可控&#xff0c;所面临的损失也是不可预估的。就像最近…

【Excel】求和带文字的数据

目录标题 1. 给出样例2. CtrlE3. CtrlH → A替换为 → 全部替换 1. 给出样例 2. CtrlE 3. CtrlH → A替换为 → 全部替换

从零开始的python学习生活

pycharm部分好用快捷键 变量名的定义 与之前学习过的语言有所不同的是&#xff0c;python中变量名的定义更加的简洁 such as 整形。浮点型和字符串的定义 money50 haha13.14 gaga"hello"字符串的定义依然是需要加上引号&#xff0c;也不需要写&#xff1b;了 字符…

重温express

前言 很久之前囫囵吞枣的学过一点node&#xff0c;最近决定用nodevue写个博客项目&#xff0c;所以重新学习了express的相关内容。 初始搭建 创建项目 npm init给项目命名创建项目终端进入项目&#xff0c;安装express依赖&#xff0c;npm i expressjs文件中引入express使用…

「C++系列」C++ 常量知识点-细致讲解

文章目录 一、C 常量定义1. 使用#define预处理指令2. 使用const关键字3. 局部常量4. 全局常量5. 指针常量6. 枚举&#xff08;Enumerations&#xff09;7. constexpr&#xff08;C11及以后&#xff09; 二、C 整数常量1. 十进制整数常量2. 八进制整数常量3. 十六进制整数常量4.…

RAG理论:ES混合搜索BM25+kNN(cosine)以及归一化

接前一篇:RAG实践:ES混合搜索BM25+kNN(cosine) https://blog.csdn.net/Xin_101/article/details/140230948 本文主要讲解混合搜索相关理论以及计算推导过程, 包括BM25、kNN以及ES中使用混合搜索分数计算过程。 详细讲解: (1)ES中如何通过BM25计算关键词搜索分数; (2)…

Postman使用指南①网页版使用

postman官网地址&#xff1a;Postman API Platform 进入后点击右上角免费注册&#xff0c;注册后登录 登录之后即可在网页使用&#xff0c;无需下载

static的理论学习

在说到static之前&#xff0c;需要先明确变量类型&#xff1a; 而在聊到变量类型之前我们可以将变量的两个属性好好学一学 变量的两个属性 作用域&#xff08;scope&#xff09;&#xff1a; 从内存的角度来看&#xff0c;就是变量存放在栈&#xff08;stack&#xff09;中&…