跳转到主要内容

14 UUID 与 JSONB

生成 UUID,并使用 jsonb 操作符查询结构化 JSON 文档。

PostgreSQL 中两种最受欢迎的非基础类型:uuid 用于不透明、全局唯一的标识符,jsonb 用于可建立索引并查询的二进制编码 JSON。UUID 是顺序整数 ID 的标准替代方案,适用于需要 ID 不可猜测、可在客户端生成或跨系统全局唯一的场景。对于半结构化数据,jsonb 让 PostgreSQL 足以与文档存储系统竞争。

本章要点

  • 使用 gen_random_uuid() 生成 UUID
  • 使用 jsonb 存储并查询 JSON
  • ->、->>、#>、#>> 操作符及其返回值
  • @> 包含关系操作符(以及它如何使用 GIN 索引)
  • json 与 jsonb 的区别

示例

示例 14.1 UUID 与 JSONB
-- UUID generation (built-in from PostgreSQL 13)
SELECT gen_random_uuid() AS id;

-- jsonb literal
SELECT '{"name": "Alice", "age": 30, "tags": ["admin", "beta"]}'::jsonb AS j;

-- -> returns jsonb (preserves type); ->> returns text
SELECT '{"name": "Alice"}'::jsonb -> 'name'  AS as_jsonb,
       '{"name": "Alice"}'::jsonb ->> 'name' AS as_text;

-- Array indexing (0-based)
SELECT '["a", "b", "c"]'::jsonb -> 1 AS second;

-- Path operators: #> returns jsonb, #>> returns text
SELECT '{"user": {"address": {"city": "Paris"}}}'::jsonb #>> '{user, address, city}' AS city;

-- Containment: @> checks if the right side is contained in the left
SELECT '{"name": "Alice", "age": 30}'::jsonb @> '{"name": "Alice"}'::jsonb AS contains_alice;

-- Querying jsonb columns in a real table
DROP TABLE IF EXISTS docs_example;
CREATE TABLE docs_example (id integer PRIMARY KEY, data jsonb);
INSERT INTO docs_example VALUES
  (1, '{"name": "Alice", "tags": ["admin"]}'),
  (2, '{"name": "Bob",   "tags": ["beta"]}');

SELECT id, data ->> 'name' AS name
FROM docs_example
WHERE data @> '{"tags": ["admin"]}';

gen_random_uuid() 会生成随机的版本 4 UUID。从 PostgreSQL 13 开始它就是内置函数,不需要 pgcrypto 扩展。PostgreSQL 17 没有内置的 UUIDv7 生成器;如果需要按时间排序的 UUID,请使用应用程序/库或扩展。PostgreSQL 18 新增 uuidv7()。bigint 标识列也是一种选择。

对于 JSON,优先使用 jsonb 而不是 json。json 会原样存储输入文本(包括空白和键顺序);jsonb 会解析、规范化并以二进制形式存储。通常对 jsonb 建立索引并进行高效查询,几乎所有应用都使用它。只有在需要逐字节保留输入(这种情况很少见)时,才使用普通 json。

-> 操作符提取并返回 jsonb。->> 提取并返回 text(去掉 JSON 引号)。#> 和 #>> 使用路径(由键/下标组成的数组)。@>(“包含”)测试左值是否包含右值——它是 JSON 查询的基础。

运行示例

运行方式:

$ psql -f source/uuid-jsonb.sql postgres
                  id
--------------------------------------
 xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx
(1 row)
 ...
 contains_alice
----------------
 t
(1 row)
 ...

常见陷阱

  • 将 data -> 'name' 与字符串比较:data -> 'name' = '"Alice"' 可以工作(注意带 JSON 引号的字符串),但通常应该使用 data ->> 'name' = 'Alice'。
  • 忘记建立索引:对没有 GIN 索引的 jsonb 列进行过滤会执行全表扫描。CREATE INDEX ON docs_example USING gin (data); 让 @> 及类似查询能够使用索引。
  • 就地修改 jsonb:UPDATE t SET data = data || '{"k": "v"}'::jsonb 会添加或替换顶层键;jsonb_set 可以修改嵌套路径。
  • jsonb 内部的模式漂移:它很灵活,但你仍需知道其中有什么。记录预期结构,并在应用层或使用 CHECK 约束进行验证。
提示

只为你查询的 JSON 部分建立索引。与其给整个 data 列建立 GIN 索引,不如给单一路径建立索引:CREATE INDEX ON docs_example ((data ->> 'name'));。这是一个 B-树表达式索引——小巧、快速且精确。

动手练习

使用 ->> 以文本形式获取名称:SELECT '{"name": "Alice"}'::jsonb ->> 'name';。插入一条带有嵌套数据的记录,并使用 #>> 查询嵌套字段。然后在 data 上建立 GIN 索引,并使用 @> 进行包含查询。

下载

SQL 文件:uuid-jsonb.sql · 全部示例文件 · 准备工作