DevToolBox免费
博客

PostgreSQL JSONB 指南:查询、索引与全文搜索

13 分钟作者 DevToolBox

PostgreSQL JSONB(二进制 JSON)是现代 PostgreSQL 中最强大的功能之一。与以文本形式存储的 JSON 类型不同,JSONB 存储解析后的二进制数据——支持快速查询、索引和高效存储。本指南涵盖从基本操作到高级索引策略和性能优化的所有内容。

JSON vs JSONB:使用哪个?

PostgreSQL 有两种 JSON 类型:json 和 jsonb。对于几乎所有用例,请使用 jsonb。

Featurejsonjsonb (recommended)
Storage formatText (verbatim)Binary (parsed)
Write speedFasterSlightly slower
Read speedSlower (re-parse)Faster
GIN indexingNoYes
Duplicate keysPreservedLast value wins
Key orderingPreservedSorted
Operators-> and ->>All operators

创建表并插入 JSONB 数据

JSONB 列存储任何有效的 JSON 值。键在存储时会被排序,重复键会被删除。

-- Create a table with JSONB column
CREATE TABLE products (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    metadata    JSONB,
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- Insert JSONB data
INSERT INTO products (name, metadata) VALUES
    ('Widget A', '{"price": 9.99, "category": "widgets", "tags": ["sale", "new"], "specs": {"weight": 0.5, "color": "red"}}'),
    ('Gadget B', '{"price": 24.99, "category": "gadgets", "tags": ["popular"], "specs": {"weight": 1.2, "color": "blue"}, "stock": 150}'),
    ('Doohickey C', '{"price": 4.99, "category": "widgets", "tags": ["sale"], "specs": {"weight": 0.1, "color": "green"}, "rating": 4.5}');

查询 JSONB:运算符参考

PostgreSQL 提供了丰富的运算符来访问和过滤 JSONB 数据。

-- Access a top-level key (returns JSONB)
SELECT metadata -> 'price' FROM products;
-- Returns: 9.99, 24.99, 4.99 (as JSONB)

-- Access a key as text (use ->>)
SELECT metadata ->> 'category' FROM products;
-- Returns: 'widgets', 'gadgets', 'widgets' (as text)

-- Nested access
SELECT metadata -> 'specs' -> 'color' FROM products;
SELECT metadata #> '{specs,color}' FROM products;        -- path operator
SELECT metadata #>> '{specs,color}' FROM products;       -- path as text

-- Filter rows by JSONB value
SELECT name FROM products WHERE metadata ->> 'category' = 'widgets';

-- Filter by nested value
SELECT name FROM products WHERE (metadata -> 'specs' ->> 'weight')::float > 0.5;

-- Check if key exists
SELECT name FROM products WHERE metadata ? 'stock';

-- Check if ANY key in array exists
SELECT name FROM products WHERE metadata ?| ARRAY['stock', 'rating'];

-- Check if ALL keys exist
SELECT name FROM products WHERE metadata ?& ARRAY['price', 'category'];

-- Containment: does JSONB contain this sub-object?
SELECT name FROM products WHERE metadata @> '{"category": "widgets"}';

-- Is JSONB contained by?
SELECT name FROM products WHERE '{"category": "widgets"}' <@ metadata;

JSONB 运算符快速参考

OperatorExampleDescription
->metadata -> 'key'Get JSONB value by key
->>metadata ->> 'key'Get text value by key
#>metadata #> '{a,b}'Get JSONB by path
#>>metadata #>> '{a,b}'Get text by path
@>metadata @> '{"k":"v"}'Containment (uses GIN)
<@'{"k":"v"}' <@ metadataIs contained by
?metadata ? 'key'Key exists
?|metadata ?| ARRAY['a','b']Any key exists
?&metadata ?& ARRAY['a','b']All keys exist
||metadata || '{"new":1}'Concatenate/merge
-metadata - 'key'Remove key
#-metadata #- '{a,b}'Remove by path

修改 JSONB 数据

在不替换整个值的情况下更新、添加和删除 JSONB 列中的字段。

-- Update a specific key (PostgreSQL 14+: subscript syntax)
UPDATE products
SET metadata['price'] = '19.99'
WHERE name = 'Widget A';

-- jsonb_set: update nested key (older syntax, all versions)
UPDATE products
SET metadata = jsonb_set(metadata, '{price}', '19.99')
WHERE name = 'Widget A';

-- Update nested path
UPDATE products
SET metadata = jsonb_set(metadata, '{specs,color}', '"black"')
WHERE name = 'Widget A';

-- Add a new key
UPDATE products
SET metadata = metadata || '{"discount": 0.1}'::jsonb
WHERE name = 'Widget A';

-- Remove a key
UPDATE products
SET metadata = metadata - 'stock'
WHERE name = 'Gadget B';

-- Remove multiple keys
UPDATE products
SET metadata = metadata - ARRAY['stock', 'rating'];

-- Remove nested key
UPDATE products
SET metadata = metadata #- '{specs,weight}';

JSONB 索引以提升性能

正确的索引策略可以使 JSONB 查询与常规列上的查询一样快。GIN 索引是 JSONB 的主力。

-- GIN index: most useful for JSONB (supports ?, ?|, ?&, @>, <@)
CREATE INDEX idx_products_metadata ON products USING GIN (metadata);

-- GIN index on a specific key (for equality queries on that key)
CREATE INDEX idx_products_category ON products
    USING GIN ((metadata -> 'category'));

-- B-tree index on extracted value (for range queries)
CREATE INDEX idx_products_price ON products
    USING BTREE ((( metadata ->> 'price' )::numeric));

-- Expression index for a nested text field
CREATE INDEX idx_products_color ON products
    USING BTREE ((metadata #>> '{specs,color}'));

-- Check index usage
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM products WHERE metadata @> '{"category": "widgets"}';

聚合和 JSONB 函数

PostgreSQL 提供了强大的函数来聚合、展开和构造 JSONB 值。

-- jsonb_agg: aggregate rows into a JSON array
SELECT
    metadata ->> 'category' AS category,
    jsonb_agg(name ORDER BY name) AS product_names,
    COUNT(*) AS count,
    AVG((metadata ->> 'price')::numeric) AS avg_price
FROM products
GROUP BY metadata ->> 'category';

-- jsonb_object_agg: build a JSON object from key-value pairs
SELECT jsonb_object_agg(name, metadata ->> 'price') AS price_map
FROM products;

-- jsonb_array_elements: expand a JSONB array into rows
SELECT
    name,
    jsonb_array_elements_text(metadata -> 'tags') AS tag
FROM products;

-- jsonb_each: expand a JSONB object into key-value rows
SELECT
    name,
    key,
    value
FROM products,
     jsonb_each(metadata -> 'specs');

-- jsonb_build_object: construct JSONB from expressions
SELECT jsonb_build_object(
    'id', id,
    'name', name,
    'price', (metadata ->> 'price')::numeric,
    'in_stock', metadata ? 'stock'
) AS product_summary
FROM products;

性能技巧

  • 始终在频繁查询的 JSONB 列上创建 GIN 索引。没有索引,JSONB 查询需要顺序扫描。
  • 使用生成列将频繁查询的字段提取为常规列:GENERATED ALWAYS AS (metadata ->> 'category') STORED。
  • 使用 jsonb 而非 json——读取更快(二进制格式,无需重新解析)且支持索引。
  • 避免深层嵌套。扁平的 JSONB 结构更易于索引和查询。考虑对非常深的结构进行规范化。
  • 使用 @>(包含)过滤已知子对象——它使用 GIN 索引。避免使用 ->> 和 LIKE 进行文本搜索;改用全文搜索。

常见问题

什么时候应该使用 JSONB 而非关系列?

在以下情况使用 JSONB:每行模式不同的半结构化数据、存储配置或设置、事件载荷或元数据、模式尚未固定的原型开发。在以下情况使用常规列:数据高度结构化且统一、需要复杂 JOIN、或数据频繁变化且需要严格事务保证。

如何在 JSONB 字符串值中搜索文本?

对于 JSONB 字符串内的全文搜索,使用:to_tsvector('english', metadata ->> 'description') @@ to_tsquery('search term')。在 tsvector 表达式上创建 GIN 索引。对于简单的子字符串匹配,使用 metadata ->> 'field' LIKE '%pattern%',但这不能使用 GIN 索引。

JSONB 值的最大大小是多少?

PostgreSQL 中单个 JSONB 值最大可达 1 GB。但是,出于实际性能原因,当单个值小于 1 MB 时,JSONB 列效果最佳。大型 JSONB 值会减慢更新(必须重写整个值)和索引速度。

如何在 PostgreSQL 中验证 JSONB 模式?

PostgreSQL 16+ 通过 jsonb_matches_schema() 函数支持 JSON Schema 验证。对于旧版本,使用带有自定义函数的 CHECK 约束。示例:ALTER TABLE products ADD CONSTRAINT check_metadata CHECK (jsonb_typeof(metadata -> 'price') = 'number');

JSONB 与 MongoDB 等专用文档数据库相比如何?

JSONB 让你在 PostgreSQL 中存储文档,将文档数据库的灵活性与 ACID 事务、复杂 JOIN、全文搜索以及所有其他 PostgreSQL 功能结合起来。MongoDB 提供原生文档优先查询和水平分片。对于大多数已经使用 PostgreSQL 的应用,JSONB 优于添加单独的数据库。

相关工具

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON Formatter🔷JSON to TypeScript OnlineSQLSQL Formatter

相关文章

GraphQL vs REST API:2026 年该用哪个?

深入比较 GraphQL 和 REST API,附代码示例。学习架构差异、数据获取模式、缓存策略,以及何时选择哪种方案。

API 限流指南:策略、算法与实现

API 限流完整指南。学习令牌桶、滑动窗口、漏桶算法及代码示例。包含 Express.js 中间件、Redis 分布式限流和最佳实践。