PostgreSQL,NoSQL 数据库
过去几年计算机世界中最有趣的趋势之一是 NoSQL 数据库的快速增长。 “NoSQL”这个术语可能是准确的,因为 NoSQL 数据库不使用 SQL 来存储和检索数据,但这几乎是它们共同点的终点。 NoSQL 数据库的范围从键值存储到列式数据库,再到文档数据库和图形数据库。
从表面上看,没有什么比 NoSQL 数据库听起来更自然或更合理的了。“阻抗失配”通常被用来描述编程语言和数据库之间的差异,这意味着我们通常必须使用两种不同的语言和两种不同的范式来工作。在我们的程序中,我们思考和使用对象,并仔细地构建它们。然后,我们解构这些对象,将它们变成数据库中的二维表。我可以在数据库中像在程序中一样操作对象的想法在许多层面上都很有吸引力。
在某些方面,这是数据库的圣杯:我们想要一种既坚如磐石、可靠,又可扩展到现代 Web 应用程序所需的大规模,并且对我们程序员来说也很方便的东西。一种流行的解决方案是 ORM(对象关系映射器),它允许我们使用对象编写程序。然后,ORM 将这些对象和方法调用转换为适当的 SQL,并将其传递给数据库。ORM 确实使使用关系数据库更加方便,至少在简单查询方面是这样。在一定程度上,它们还提高了我们代码的可读性,因为我们可以坚持使用对象,而无需使用语言和范式的组合。
但是 ORM 也有其问题,这在很大程度上是因为它们会使我们看不到数据库的内部工作原理。NoSQL 的倡导者说,他们的数据库已经解决了这些问题,使他们能够停留在单一语言中。实际上,这并不完全正确。MongoDB 有其自己的类似 SQL 的查询语言,而 CouchDB 使用 JavaScript。但是,对于许多 NoSQL 数据库,都有类似的 ORM 式适配器,允许开发人员在开发时停留在单一语言和范式中。
然而,最终的问题是,NoSQL 数据库的优点是否超过了它们的问题。我基本上得出的结论是,除了键值存储之外,答案是“否”——关系数据库通常会是更好的解决方案。我所说的“更好”是指关系数据库比许多 NoSQL 同类数据库更可靠,甚至更具可扩展性。当然,您可能需要努力才能使扩展正确工作,但没有神奇的解决方案。仅在过去的几个月中,我就获得了几位新客户,他们决定从 NoSQL 解决方案迁移到关系数据库,并且需要架构、开发或优化方面的帮助。
问题是,即使是最顽固的关系数据库爱好者也会承认,有时 NoSQL 数据存储也很方便。随着 Web API 中 JSON 的增长,如果能够将结果集存储在一种能够理解该格式并允许我从中搜索和检索的存储类型中,那将是很不错的。即使像 Redis 这样的键值存储功能强大且速度快,但有时我也希望将键值对连接到数据库中其他关系(表)中的数据。
如果这描述了您的困境,我为您带来好消息。在我撰写本文时,PostgreSQL,一个令人惊叹的数据库和开源项目,即将发布 9.4 版本。这个新版本像所有其他 PostgreSQL 版本一样,包含许多优化、改进和可用性功能。但是,对我来说,最吸引人的两个功能是 HStore 和 JSONB,这些功能实际上将 PostgreSQL 变成了 NoSQL 数据库。
好吧,也许我在这里有点夸张了。PostgreSQL 过去是并将永远是关系型和事务型的,添加这些新的数据类型并没有改变这一点。但是,在 PostgreSQL 中拥有键值存储为开发人员开辟了许多新的可能性。JSONB 是 JSON 存储的二进制版本,支持索引和大量运算符,它将 PostgreSQL 变成了一个文档数据库,尽管它还具有其他一些功能。
在本文中,我将介绍 PostgreSQL 9.4 中包含的这些 NoSQL 功能,该版本很可能在本期Linux Journal发布之前发布。虽然并非每个应用程序都需要这些功能,但它们可能很有用——并且随着 PostgreSQL 最新版本的发布,性能也得到了显着提高。
HStorePostgreSQL 中最有趣的新发展之一是 HStore,它在 PostgreSQL 环境中提供了键值存储。与我最初的想法相反,这并不意味着 PostgreSQL 将特定表视为键值存储。相反,HStore 是一种数据类型,类似于 INTEGER
、TEXT
和 XML
。因此,表中的任何列(或列集)都可以定义为 HSTORE
类型。例如
CREATE TABLE People (
id SERIAL,
info HSTORE,
PRIMARY KEY(id)
);
完成此操作后,我可以要求 PostgreSQL 向我显示表的定义
\d people
Table "public.people"
-----------------------------------------------------------------
| Column | Type | Modifiers |
-----------------------------------------------------------------
| id | integer | not null default |
| | | ↪nextval('people_id_seq'::regclass)|
-----------------------------------------------------------------
| info | hstore | |
-----------------------------------------------------------------
Indexes:
"people_pkey" PRIMARY KEY, btree (id)
如您所见,我的“info”列的类型是 hstore。我有效地创建了一个哈希表(数据库)表。“people”表中的每一行都将有自己的哈希表,其中包含任何键和值。在这种情况下,每行都具有相同的键名,或者至少具有一些最小数量的重叠键名是典型的,但是,当然,您可以使用任何您喜欢的键和值。
HStore 列中的键和值都是文本字符串。您可以使用以下语法将哈希表分配给 HStore 列
INSERT INTO people(info) VALUES ('foo=>1, bar=>abc, baz=>stuff');
请注意,尽管此示例将三个键值对插入到 HStore 列中,但它们存储在一起,自动转换为 HStore,在逗号处拆分对,并在 => 符号处拆分每个对。
到目前为止,除了(可能)您不能在该列上使用文本函数和运算符之外,您不会看到 HStore 和 TEXT 列之间有任何区别。例如,您不能在 HStore 上使用通常连接文本字符串的 || 运算符
UPDATE People SET info = info || 'abc';
ERROR: XX000: Unexpected end of string
LINE 1: UPDATE People SET info = info || 'abc';
^
PostgreSQL 尝试将 || 运算符应用于左侧的 HStore,但在右侧的字符串中找不到键值对,从而产生错误消息。但是,您可以添加一个对,这将起作用
UPDATE People SET info = info || 'abc=>def';
与所有哈希表一样,HStore 旨在让您使用键来检索值。也就是说,每个键在每个 HStore 值中仅存在一次,尽管值可能会重复。检索值的唯一方法是通过键。您可以使用以下语法执行此操作
SELECT info->'bar' FROM People;
----------------
| ?column? | |
----------------
| abc | |
----------------
(1 row)
请注意这里的几件事。首先,列的名称保持不变,没有任何引号,就像您检索列的全部内容时一样。其次,您将键的名称放在 -> 箭头之后,这与 =>(“哈希火箭”)箭头不同,后者用于描绘 HStore 中的键值对。最后,返回的值始终为 TEXT 类型。这意味着如果您说
SELECT info->'foo' || 'a' FROM People;
----------------
| ?column? | |
----------------
| 1a | |
----------------
(1 row)
请注意,|| 运算符适用于文本值,在这里完成了它的工作。但是,这也意味着如果您尝试将您的值相乘,您将收到错误消息
SELECT info->'foo' * 5 FROM People;
info->'foo' * 5 from people;
^
Time: 5.041 ms
如果您想将 info->'foo'
检索为整数,则必须强制转换该值
SELECT (info->'foo')::integer * 5 from people;
----------------
| ?column? | |
----------------
| 5 | |
----------------
(1 row)
现在,为什么 HStore 如此令人兴奋?特别是,如果您是一位重视规范化的数据库人员,您可能会想知道为什么有人甚至想要这种数据存储,而不是一个良好规范化的表或表集。
当然,答案是数据库有很多不同的用途,其中一些用途可能更适合 HStore。我永远不会建议在这种东西中存储重要数据,但也许您想跟踪用户会话信息,而无需将其保存在二进制对象中。
现在,HStore 对于 PostgreSQL 来说并不是什么新鲜事物。9.4 版本中的重大新闻是 GiN 和 GIST 索引现在支持 HStore 列,并且它们以极高的效率和速度执行此操作。
我计划在哪里使用 HStore?老实说,我还不确定。我觉得这是一种我可能在某个时候想要使用的数据类型,但就目前而言,它只是一个额外的有用、高效的工具,我可以将其放入我的编程工具箱中。事实上,它现在非常高效,并且其运算符可以利用改进的索引,这意味着 HStore 不仅方便,而且速度也很快。
JSON 和 JSONB长期以来,在 PostgreSQL 中存储 JSON 一直是可能的。毕竟,JSON 只是 JavaScript 对象(“JavaScript 对象表示法”)的文本表示形式,这意味着它们实际上是字符串。但是,当然,当您在 PostgreSQL 中存储数据时,您希望获得更多信息。您希望确保存储的数据有效,并使用 PostgreSQL 的运算符来检索和处理该数据。
PostgreSQL 已经有一个 JSON 数据类型好几年了。该数据类型最初是 JSON 的简单文本表示形式,它会检查有效内容,但仅此而已。PostgreSQL 9.3 版本允许您在 JSON 列上使用更多运算符,从而可以相对轻松地检索数据的特定部分。
然而,JSON 数据的存储和检索从来都不是那么有效,并且与 JSON 相关的运算符在这方面尤其糟糕。所以,是的,您可以在 JSON 列中查找特定的名称或值,但这可能需要一段时间。
这种情况在 9.4 版本中发生了变化,它引入了 JSONB 数据类型,该类型以二进制形式存储 JSON 数据,使其比文本形式更紧凑、更高效。此外,现在能够与 HStore 数据良好配合的相同 GIN 和 GIST 索引也能够与 JSONB 数据良好且快速地配合使用。因此,您可以像使用 MongoDB 等文档数据库一样轻松(或更轻松)地从 JSONB 文档中搜索和检索文本。
我已经开始在我的某些工作中使用 JSONB。例如,我正在进行的一个项目通过 API 联系远程服务器。服务器以 JSON 格式返回其响应,其中包含大量名称-值对,其中一些是嵌套的。(我应该注意,如果您首先获得客户的批准并解释风险和收益,那么使用 PostgreSQL 的 beta 版本或任何其他基础设施技术才是一个好主意。)
现在,我是规范化数据的忠实拥护者。而且我不太喜欢在数据库中存储 JSON。但是,与其开始猜测我将来需要和不需要哪些数据,我决定暂时将所有内容存储在 JSONB 列中。如果并且当我确切知道我需要什么时,我将更大程度地规范化数据。
实际上,这并非完全正确。我从一开始就知道我需要从收到的响应中获得两个不同的值。但是,由于我将数据存储在 JSONB 中,我认为我只需从 JSONB 列中检索数据即可。
将数据存储在那里后,我就可以从 JSON 列中检索数据
SELECT id, email,
personal_data->>'surname' AS surname
personal_data->>'forename' as given_name
FROM ID_Checks
WHERE personal_data->>'surname' ilike '%lerner%';
使用双箭头运算符 (->>),我能够通过使用 JSON 对象的键来检索其值。请注意,如果您使用单箭头 (->),您将获得一个对象,这很可能不是您想要的。我发现文本部分确实是我大部分时间最感兴趣的。
结论人们使用 NoSQL 数据库有几个原因。其中之一是对象和表之间的阻抗失配。但另外两个常见原因是性能和便利性。事实证明,得益于改进的数据类型和索引,现代版本的 PostgreSQL 提供了出色的性能。但它们也提供了极大的便利性,让您可以轻松、高效且自然地设置、检索和删除 JSON 和键值数据。
我不会完全否定整个 NoSQL 运动。但我要说的是,下次您考虑使用 NoSQL 数据库时,请考虑使用一个已经可以满足您所有需求的数据库,而您可能已经在使用它了——PostgreSQL。
资源关于 PostgreSQL 的 GiN 和 GIST 索引改进的博客文章,这些改进影响了 JSON 和 HStore 类型
PostgreSQL 文档位于 https://postgresql.ac.cn/docs,它包括 HStore 和 JSONB 各自的几个部分。