与经典规范化表相比,postgres JSON 索引是不是足够高效?

Posted

技术标签:

【中文标题】与经典规范化表相比,postgres JSON 索引是不是足够高效?【英文标题】:Are postgres JSON indexes efficient enough compared with classic normalized tables?与经典规范化表相比,postgres JSON 索引是否足够高效? 【发布时间】:2013-09-18 22:21:38 【问题描述】:

当前的 Postgresql 版本已经为 JSON 内容引入了各种功能,但我担心我是否真的应该使用它们 - 我的意思是,还没有建立关于哪些有效和哪些无效的“最佳实践”,或者至少没找到。

我有一个具体的例子——我有一个关于对象的表,其中包含该对象的备用名称列表。所有这些数据也将包含在 JSON 列中以供检索。例如(跳过所有其他不相关的字段)。

create table stuff (id serial primary key, data json);
insert into stuff(data) values('"AltNames":["Name1","Name2","Name3"]')

我需要一些查询,格式为“列出所有替代名称之一为 'foobar' 的对象”。预期的表大小约为几百万条记录。 可以使用 Postgres JSON 查询,也可以对其进行索引(例如Index for finding an element in a JSON array)。但是,应该这样做还是不建议这样做的不正当的解决方法?

当然,经典的替代方法是为该一对多关系添加一个附加表,其中包含主表的名称和外键;它的表现是很好理解的。但是,这有其自身的缺点,因为这意味着该表和 JSON 之间的数据重复(可能存在完整性风险);或者在每次请求时动态创建 JSON 返回数据,这有其自身的性能损失。

【问题讨论】:

密切相关:***.com/questions/18404055/… 如果你只想存储一个数组,为什么不使用text[]而不是JSON? thebuild.com/presentations/pg-as-nosql-pgday-fosdem-2013.pdf 【参考方案1】:

我需要一些查询,格式为“列出所有替代名称之一为 'foobar' 的对象”。预期的表大小约为几百万条记录。可以使用 Postgres JSON 查询,也可以对其进行索引(例如,在 JSON 数组中查找元素的索引)。但是,应该这样做还是不建议这样做的不正当的解决方法?

可以这样做,但这并不意味着你应该这样做。从某种意义上说,最佳实践已经得到了很好的记录(例如,参见使用 hstore 与使用 XML 与使用 EAV 与使用单独的表)和一个新的数据类型,对于所有意图和实际目的(除了验证和语法),没有什么不同来自之前的非结构化或半结构化选项。

换个说法,就是一头换了新妆的老猪。

JSON 提供了使用反向搜索树索引的能力,就像 hstore、数组类型和 tsvector 一样。它们工作正常,但请记住,它们主要用于提取按距离排序的邻域中的点(想想几何类型),而不是按字典顺序提取值列表。

为了说明,以 Roman 的回答概述的两个计划:

索引扫描直接遍历磁盘页面,按索引指示的顺序检索行。 位图索引扫描首先识别可能包含一行的每个磁盘页面,并在它们出现在磁盘上时读取它们,就好像它是一样(实际上,就像) 执行跳过无用区域的序列扫描。

回到您的问题:如果您将 Postgres 表用作巨型 JSON 存储,那么杂乱和过大的倒排树索引确实会提高您的应用程序的性能。但它们也不是灵丹妙药,在处理瓶颈时它们不会让您达到正确的关系设计。

归根结底,这与您决定使用 hstore 或 EAV 时得到的结果没有什么不同:

    如果它需要一个索引(即它经常出现在 where 子句中,或者更重要的是出现在 join 子句中),您可能希望将数据放在单独的字段中。 如果它主要是装饰性的,那么 JSON/hstore/EAV/XML/whatever-makes-you-sleep-at-night 可以正常工作。

【讨论】:

+1 丹尼斯,好一个。我必须在 PostgreSQL 上为此测试 hstore 和 xml。【参考方案2】:

我觉得值得一试。我创建了一些测试(100000 条记录,JSON 数组中的约 10 个元素)并检查了它是如何工作的:

create table test1 (id serial primary key, data json);
create table test1_altnames (id int, name text);

create or replace function array_from_json(_j json)
returns text[] as
$func$
    select array_agg(x.elem::text)
    from json_array_elements(_j) as x(elem)
$func$
language sql immutable;

with cte as (
    select
        (random() * 100000)::int as grp, (random() * 1000000)::int as name
    from generate_series(1, 1000000)
), cte2 as (
    select
        array_agg(Name) as "AltNames"
    from cte
    group by grp
)
insert into test1 (data)
select row_to_json(t)
from cte2 as t

insert into test1_altnames (id, name)
select id, json_array_elements(data->'AltNames')::text
from test1

create index ix_test1 on test1 using gin(array_from_json(data->'AltNames'));
create index ix_test1_altnames on test1_altnames (name);

查询 JSON(在我的机器上 30ms):

select * from test1 where '489147' <@ array_from_json(data->'AltNames');

"Bitmap Heap Scan on test1  (cost=224.13..1551.41 rows=500 width=36)"
"  Recheck Cond: ('489147'::text[] <@ array_from_json((data -> 'AltNames'::text)))"
"  ->  Bitmap Index Scan on ix_test1  (cost=0.00..224.00 rows=500 width=0)"
"        Index Cond: ('489147'::text[] <@ array_from_json((data -> 'AltNames'::text)))"

使用名称查询表(在我的机器上15ms):

select * from test1 as t where t.id in (select tt.id from test1_altnames as tt where tt.name = '489147');

"Nested Loop  (cost=12.76..20.80 rows=2 width=36)"
"  ->  HashAggregate  (cost=12.46..12.47 rows=1 width=4)"
"        ->  Index Scan using ix_test1_altnames on test1_altnames tt  (cost=0.42..12.46 rows=2 width=4)"
"              Index Cond: (name = '489147'::text)"
"  ->  Index Scan using test1_pkey on test1 t  (cost=0.29..8.31 rows=1 width=36)"
"        Index Cond: (id = tt.id)"

另外我必须注意,将行插入/删除带有名称 (test1_altnames) 的表需要一些成本,因此它比仅选择行要复杂一些。我个人喜欢 JSON 的解决方案。

【讨论】:

在我的机器上使用 PostgreSQL 9.4 查询要快得多:两个查询大约 1 毫秒:D 1ms、15ms或30ms的测量值无关紧要,因为它们受一级CPU缓存、二级、RAM延迟、交换、系统负载、HDD/SSD/SATA/SATA2/SATA3的影响很大.......等等。您总是必须在大量数据上并且可能多次测量这些事情。

以上是关于与经典规范化表相比,postgres JSON 索引是不是足够高效?的主要内容,如果未能解决你的问题,请参考以下文章

数据库规范化与经典三范式

SQL Server 内存优化表 - 与临时表相比性能较差

Postgres 从 json 字段创建表

Sql Server内存优化表-与临时表相比性能较差

Postgres 9.5 - 查询嵌套 JSON 元素的数组长度

《数据库设计入门经典》读书笔记——第三章:工作场所中的数据库建模