是否应该将PostgreSQL作为你的下一个JSON数据库?

发表时间: 2018-01-24 08:08

随着最新版本的PostgreSQL获得更多的JSON功能,我们被问及PostgreSQL是否可以将MongoDB替换为JSON数据库。有一个简短的答案,但我们宁愿向你展示。啊,来自观众的一个问题

“没有PostgreSQL已经有一些JSON支持?”

是的,它确实。在PostgreSQL 9.4之前有JSON数据类型,并且仍然可用。它可以让你这样做:

>CREATE TABLE justjson ( id INTEGER, doc JSON)>INSERT INTO justjson VALUES ( 1, '{ "name":"fred", "address":{ "line1":"52 The Elms", "line2":"Elmstreet", "postcode":"ES1 1ES" } }');

将JSON数据的原始文本存储在数据库中,并保留空白并保留所有键和任何重复键的所有命令。让我们看看数据显示:

>SELECT * FROM justjson;id | doc----+--------------------------------- 1 | { + | "name":"fred", + | "address":{ + | "line1":"52 The Elms", + | "line2":"Elmstreet", + | "postcode":"ES1 1ES" + | } + | }(1 row)

它存储了源数据的精确副本。但是我们仍然可以从中提取数据。为此,有一组JSON运算符让我们引用JSON文档中的元素。所以说我们只是想要地址部分,我们可以这样做:

select doc->>'address' FROM justjson;  ?column?--------------------------------- { + "line1":"52 The Elms", + "line2":"Elmstreet", + "postcode":"ES1 1ES" + }(1 row)

->>运营商表示文档中,查找JSON对象有以下字段名,并返回它作为文本。有了一个数字,它会把它当作一个数组索引,但是仍然以文本形式返回值。还有->一起去->>不这样做转换为文本。我们需要这样的,所以我们可以导航到这样的JSON对象:

select doc->'address'->>'postcode' FROM justjson; ?column?---------- ES1 1ES(1 row)

虽然有一个更简短的形式,我们可以指定一个数据的路径,我们正在使用后#>>,像这样的数组:

select doc#>>'{address,postcode}' FROM justjson; ?column?---------- ES1 1ES(1 row)

通过保留整个文档的JSON数据类型,可以轻松地处理JSON文档的精确副本,并在不丢失的情况下传递它们。但是,正确性会带来成本和效率的损失,因此无法进行索引。因此,虽然保存和解析JSON文档非常方便,但仍然有很大的改进余地,而且JSONB也是如此。

“JSONB有什么不同?

那么,使用JSONB将JSON文档转换为键/值数据对的层次结构。所有的空白都被丢弃,只有一组重复键中的最后一个值被使用,并且键的顺序会丢失到存储它们的散列所规定的结构中。如果我们创建一个我们刚创建的JSONB版本的表,插入一些数据并查看它:

>CREATE TABLE justjsonb ( id INTEGER, doc JSONB)>INSERT INTO justjsonb VALUES ( 1, '{ "name":"fred", "address":{ "line1":"52 The Elms", "line2":"Elmstreet", "postcode":"ES1 1ES" } }');>SELECT * FROM justjsonb;id | doc----+---------------------------------------------------------------------------------------------------- 1 | {"name": "fred", "address": {"line1": "52 The Elms", "line2": "Elmstreet", "postcode": "ES1 1ES"}}(1 row)

我们可以看到所有的textyness数据已经消失了,取而代之的是JSON文档中保存的数据。这种剥离数据意味着JSONB表示将解析工作移动到插入数据的时候,但是解除了解析任务后的任何数据访问。

“这看起来有点像PostgreSQL中的HSTORE,虽然”

看作键/值对,那么JSONB数据类型看起来有点像PostgreSQL HSTORE扩展。这是用于存储键/值对的数据类型,但是它是JSONB(和JSON)在核心中的扩展,在JSON文档可以具有嵌套元素的数据结构方面,HSTORE是一个深度。此外,HSTORE只存储字符串,而JSONB理解字符串和全部的JSON数字。

“那么JSONB究竟给我带来了什么呢?”

索引,索引到处。你实际上不能在PostgreSQL中索引一个JSON数据类型。你可以使用表达式索引为它创建一个索引,但是这将会涵盖你在表达式中的任何东西。所以如果我们想要我们可以做的

create index justjson_postcode on justjson ((doc->'address'->>'postcode'));

和邮政编码,没有别的将被索引。

使用JSONB,支持GIN索引; 一个广义倒置索引。这为您提供了另一组查询运算符。这些@>包含JSON,<@包含,?测试字符串存在,?|任何字符串存在和?&所有字符串存在。

有两种类型的索引可以使用缺省索引来创建,称为json_ops索引,它支持所有这些操作符以及jsonb_path_ops仅支持的索引@>。默认索引为JSON中的每个键和值创建一个索引项,而jsonb_path_ops唯一创建一个导致值和值本身的键的哈希值,这比更复杂的默认值更紧凑和更快处理。但是默认情况下提供了更多的操作,但消耗更多的空间。在向我们的表中添加一些数据之后,我们可以选择查找特定的邮政编码。如果我们创建默认的GIN JSON索引并执行查询:

explain select * from justjsonb where doc @> '{ "address": { "postcode":"HA36CC" } }';  QUERY PLAN----------------------------------------------------------------- Seq Scan on justjsonb (cost=0.00..3171.14 rows=100 width=123) Filter: (doc @> '{"address": {"postcode": "HA36CC"}}'::jsonb)(2 rows)

我们可以看到它会顺序扫描表格。现在,如果我们创建一个默认的JSON GIN索引,我们可以看到它的差异:

> create index justjsonb_gin on justjsonb using gin (doc);> explain select * from justjsonb where doc @> '{ "address": { "postcode":"HA36CC" } }'; QUERY PLAN------------------------------------------------------------------------------- Bitmap Heap Scan on justjsonb (cost=40.78..367.62 rows=100 width=123) Recheck Cond: (doc @> '{"address": {"postcode": "HA36CC"}}'::jsonb) -> Bitmap Index Scan on justjsonb_gin (cost=0.00..40.75 rows=100 width=0) Index Cond: (doc @> '{"address": {"postcode": "HA36CC"}}'::jsonb)(4 rows)

这是更有效的搜索,你可以告诉成本较低。但隐藏的成本是在指数的大小。在这种情况下,它是数据大小的41%。让我们删除该索引,并用jsonb_path_ops GIN索引重复该过程。

> create index justjsonb_gin on justjsonb using gin (doc jsonb_path_ops);> explain select * from justjsonb where doc @> '{ "address": { "postcode":"HA36CC" } }'; QUERY PLAN------------------------------------------------------------------------------- Bitmap Heap Scan on justjsonb (cost=16.78..343.62 rows=100 width=123) Recheck Cond: (doc @> '{"address": {"postcode": "HA36CC"}}'::jsonb) -> Bitmap Index Scan on justjsonb_gin (cost=0.00..16.75 rows=100 width=0) Index Cond: (doc @> '{"address": {"postcode": "HA36CC"}}'::jsonb)(4 rows)

总成本稍低,通常指数规模应该小很多。这将是平衡索引速度和大小的经典任务。但是它比顺序扫描更有效率。

“我应该用这个作为我的JSON数据库吗?”

如果你更新了你的JSON文件,答案是否定的。PostgreSQL非常擅长存储和检索JSON文档及其字段。但即使您可以单独处理JSON文档中的各个字段,也无法更新单个字段。那么,实际上你可以,但通过提取整个JSON文档,附加新的值并写回来,让JSON解析器整理重复。但是,你可能不想依赖这个。

如果你的活动数据在关系模式中舒适地存在,并且JSON内容是这个数据的一个队列,那么PostgreSQL应该没问题,而且它的JSONB表示和索引功能更有效率。如果您的数据模型是可变文档集合的数据模型,那么您可能要查看一个主要围绕像MongoDB或RethinkDB这样的JSON文档进行工程设计的数据库。