postgresql - 树搜索的数据类型或结构
问题描述
我正在使用一个大数据结构,它通过字符串标签描述项目树,例如文件系统中的路径字符串。有 ~198,000 个树元素和 ~9,100,000 个叶元素。
实施选项:
按似乎是自然选择的数据类型。
ltree
通过规范化表结构:为每个路径创建一个序列索引等和递归搜索
我想,在实施后,我可以比较两者EXPLAIN ANALYZE
。但是,在实施之前,是否可以预测差异或估计(速度和额外磁盘消耗)性能?
笔记和测试
关于查询(要求),它是开放的:当您通过“字符串路径”表示分层信息时,“分层搜索引擎”必须具有表现力,ltree、正则表达式和有时 LIKE 运算符都具有表现力。
用于测试CREATE TABLE test (path ltree)
与 ~198,000 个树元素相似的指南:
CREATE INDEX path_gist_idx ON test USING GIST (path)
; -- consumes ~3G
SELECT count(*) n
FROM test WHERE path ~ 'first.second.*.etc_etc_etc.*'
; -- n= 149068
EXPLAIN ANALYSE
SELECT count(*) n
FROM test WHERE path ~ 'first.second.*.etc_etc_etc.*'
; -- Planning Time: 0.075 ms
-- Execution Time: 1317.443 ms
解决方案
正如这个问题/解决方案所推荐的,规范化表的最佳解决方案似乎是“闭包表” 。
在其他索引策略上,我们可以使用这个问题/答案的线索,建议尝试pg_trgm
模块。另见https://www.postgresql.org/docs/current/pgtrgm.html
让我们检查所有解决方案。
蛮力(索引)
“蛮力”的优点是不需要改变数据结构,只添加一个索引,在大数据上下文中我们有一些额外的磁盘空间。所以,只测试了索引,没有拆分成真正的树和指针。
测试ltree
搜索
请参阅问题主体的测试。执行时间:~1300 毫秒。
良好的层次搜索运算符。唯一的问题是索引需要一个新列,当字符串必须保留以供其他用途时,或者不是ltree
-ingestion 格式。我使用了转换
trim(
replace(
regexp_replace(text_path,'[^A-Z0-9_/]+','_','ig'), -- clean
'/', -- the path separator at text_path
'.' -- the ltree separator
),
'.' -- clean
)::ltree
因此,当您转换其他问题时,它不能 100% 可逆地被 cast 重用ltree2text()
。
使用 LIKE 运算符测试pg_trgm
搜索
这是更快的方法:~200 ms
CREATE EXTENSION pg_trgm;
CREATE INDEX textpath_gist_idx ON t2_text USING gin (txt_path gin_trgm_ops);
EXPLAIN ANALYSE
SELECT count(*) n
FROM t2_text
WHERE txt_path LIKE 'first/second/%/etc_etc_etc/%'
; -- Planning Time: 0.133 ms
-- Execution Time: 195.524 ms
-- ... BUT, returns zero on COUNT result!
索引USING gin (colName gin_trgm_ops)
使用较少的磁盘空间,~2G。
这里的问题不是性能而是它不起作用(!),并且不是“两个%
”的问题,因为对于类似的查询工作正常,所以它不可靠:
- 计数
WHERE txt_path LIKE 'first/second/%/etc_etc_etc/%'
为零。 - 计数
WHERE txt_path LIKE '%etc_etc_etc%'
是正确的。
似乎是 PostgreSQL v12 错误(其他版本相同?)。
pg_trgm
使用正则表达式测试搜索
执行时间约为 1000 毫秒,它比最可靠的ltree
LIKE运算符快一点(并且磁盘消耗更少)。
EXPLAIN ANALYSE
SELECT count(*) n
FROM t2_text
WHERE txt_path ~ '^first/second/.+/etc_etc_etc/'
-- Timing: Generation 2.312 ms, ..., Total 27.853 ms
-- Execution Time: 1017.293 ms
封闭表
需要添加一个id bigserial
if not exists 和一个新表,其祖先和后代指向原始表。它会消耗更多的磁盘空间,而且...我可以用这个模型做所有的查询吗?
它还消耗更多的程序员时间(!),没有自动化的方式(它是一个模板表,但 PostgreSQL 不提供“标准解决方案”)来创建额外的表。对于查询,没有简单的 operator。
...所以我没有添加测试,它是一个 Wiki:如果您在此处编辑某些内容,我可以添加测试以显示差异。
CREATE TABLE t2_treeclosure (
ancestor bigint NOT NULL,
descendant bigint NOT NULL,
PRIMARY KEY (ancestor,descendant),
FOREIGN KEY (ancestor) REFERENCES t2_text(id),
FOREIGN KEY (descendant) REFERENCES t2_text(id)
);
...有时可以减少原始t2_text
表减少文本的磁盘消耗,在滑倒它之后。需要一个函数来重建路径等,但如果“表关闭”比“蛮力”方法更快,则可以更快。
推荐阅读
- c - 如果它们是结构的成员,可以比较 C 中的指针吗?
- swift - 不能在其他派生属性 @count 上使用派生属性 @sum
- python - 在树上聚合节点的值
- tensorflow2.0 - 在 tensorflow 2.0 中,如何设置范围为 1000~1100 的 model.fit 标签?
- python - 高斯滤波(DoG)的差异没有给出预期的结果
- html - 用于 Bootstrap 自定义复选框的 Box-Shadow CSS
- c++ - 什么是 C++ 中的“变量”?
- javascript - 如何返回不带方括号或逗号的数组
- python - Tkinter Widget Treeview 列可以更加自定义吗?
- azure-devops - 如何从 Azure DevOps 扩展调用 Wiki REST Api?