You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL JSONB查询未用索引优化及结果不符问题

PostgreSQL JSONB GIN索引优化:匹配数组中任意元素的查询

表结构与数据

refs表包含JSONB类型列,结构及数据如下:

idjson_data(jsonb)
1{ "CorrelationId": "1111", "References": [ { "Id": "001", "Type": "RefType" }, { "Id": "002", "Type": "RefType" } ] }
2{ "CorrelationId": "2222", "References": [ { "Id": "001", "Type": "RefType" }, { "Id": "003", "Type": "RefType" } ] }

问题描述

需要返回包含任意一个指定RefType对象的行(即同时返回id=1和id=2的行),但遇到以下问题:

  • 以下查询逻辑正确,但未使用已创建的GIN索引,导致全表扫描,性能极差:
select * from refs where json_data -> 'References' @> any(array(select jsonb_build_array(refNo) from jsonb_array_elements(cast('[{"Id":"001","Type":"RefType"},{"Id":"002","Type":"RefType"}]' as jsonb)) refNo))
  • 已创建的GIN索引:
CREATE INDEX idx ON refs USING gin ((json_data -> 'References') jsonb_path_ops);
  • 以下查询能正常使用GIN索引,但逻辑不符合需求(仅返回同时包含两个指定RefType对象的行,即只返回id=1,无法返回id=2):
select * from refs where json_data -> 'References' @> '[{"Id":"001","Type":"RefType"},{"Id":"002","Type":"RefType"}]'

执行计划分析

原查询执行计划

原查询执行计划显示进行了并行全表扫描,未使用GIN索引,执行时间长达23秒:

QUERY PLAN                                                                                                                                                                                                                                                     |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Gather  (cost=1001.25..1135454.32 rows=2490762 width=486) (actual time=23804.717..23807.660 rows=0 loops=1)                                                                                                                                                    |
  Output: 
  Workers Planned: 2                                                                                                                                                                                                                                           |
  Params Evaluated: $0                                                                                                                                                                                                                                         |
  Workers Launched: 2                                                                                                                                                                                                                                          |
  Buffers: shared hit=4220 read=609812 dirtied=75                                                                                                                                                                                                              |
  I/O Timings: shared/local read=61795.046                                                                                                                                                                                                                     |
  InitPlan 1 (returns $0)                                                                                                                                                                                                                                      |
    ->  Function Scan on pg_catalog.jsonb_array_elements refno  (cost=0.00..1.25 rows=100 width=32) (actual time=0.018..0.020 rows=2 loops=1)                                                                                                                |
          Output: jsonb_build_array(refno.value)                                                                                                                                                                                                             |
          Function Call: jsonb_array_elements('[{"Id": "0002222", "Type": "RefType"}, {"Id": "0002333", "Type": "RefType"}]'::jsonb)                                                                                                                      |
  ->  Parallel Seq Scan on public.refs  (cost=0.00..885376.86 rows=1037818 width=486) (actual time=23793.829..23793.830 rows=0 loops=3)                                                                                                       |
        Output: 
        Filter: ((refs.json_data -> 'References'::text) @> ANY ($0))                                                                                                                                                              |
        Rows Removed by Filter: 8352299                                                                                                                                                                                                                        |
        Buffers: shared hit=4220 read=609812 dirtied=75                                                                                                                                                                                                        |
        I/O Timings: shared/local read=61795.046                                                                                                                                                                                                               |
        Worker 0:  actual time=23793.628..23793.629 rows=0 loops=1                                                                                                                                                                                             |
          Buffers: shared hit=2002 read=202638 dirtied=6                                                                                                                                                                                                       |
          I/O Timings: shared/local read=20565.263                                                                                                                                                                                                             |
        Worker 1:  actual time=23784.348..23784.349 rows=0 loops=1                                                                                                                                                                                             |
          Buffers: shared hit=1059 read=202832 dirtied=69                                                                                                                                                                                                      |
          I/O Timings: shared/local read=20593.289                                                                                                                                                                                                             |
Settings: effective_cache_size = '2640112kB', search_path = 'public, public, "$user"'                                                                                                                                                                          |
Query Identifier: 4447059927147613398                                                                                                                                                                                                                          |
Planning:                                                                                                                                                                                                                                                      |
  Buffers: shared hit=1                                                                                                                                                                                                                                        |
Planning Time: 0.286 ms                                                                                                                                                                                                                                        |
Execution Time: 23807.719 ms                                                                                                                                                                                                                                   |

修改后的查询执行计划

根据Laurenz Albe修改后的查询依然采用全表扫描+嵌套循环,执行时间更长(42秒),仍未利用索引:

QUERY PLAN                                                                                                                                                                                                                                                     |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Gather  (cost=1000.00..33199657.22 rows=26046052 width=518) (actual time=42826.145..42840.404 rows=1 loops=1)                                                                                                                                                  |
  Output: refs.id, refs.json_data
  Workers Planned: 2                                                                                                                                                                                                                                           |
  Workers Launched: 2                                                                                                                                                                                                                                          |
  Buffers: shared hit=3048 read=610912 dirtied=2466                                                                                                                                                                                                            |
  I/O Timings: shared/local read=25028.306                                                                                                                                                                                                                     |
  ->  Nested Loop  (cost=0.00..30594052.02 rows=10852522 width=518) (actual time=42817.831..42821.316 rows=0 loops=3)                                                                                                                                          |
        Output: refs.id, refs.json_data
        Join Filter: ((refs.json_data  -> 'References'::text) @> jsonb_build_array(refNo.value))                                                                                                                                 
        Rows Removed by Join Filter: 8352160                                                                                                                                                                                                                   |
        Buffers: shared hit=3048 read=610912 dirtied=2466                                                                                                                                                                                                      |
        I/O Timings: shared/local read=25028.306                                                                                                                                                                                                               |
        Worker 0:  actual time=42814.320..42814.322 rows=0 loops=1                                                                                                                                                                                             |
          Buffers: shared hit=1038 read=205719 dirtied=789                                                                                                                                                                                                     |
          I/O Timings: shared/local read=8403.926                                                                                                                                                                                                              |
        Worker 1:  actual time=42813.762..42813.765 rows=0 loops=1                                                                                                                                                                                             |
          Buffers: shared hit=1024 read=201674 dirtied=982                                                                                                                                                                                                     |
          I/O Timings: shared/local read=8412.231                                                                                                                                                                                                              |
        ->  Parallel Seq Scan on public.refs  (cost=0.00..722485.22 rows=10852522 width=486) (actual time=0.962..19531.915 rows=8352160 loops=3)                                                                                              |
              Output: refs.id, refs.json_data
              Buffers: shared hit=3048 read=610912 dirtied=2466                                                                                                                                                                                                |
              I/O Timings: shared/local read=25028.306                                                                                                                                                                                                         |
              Worker 0:  actual time=0.817..19559.124 rows=8448451 loops=1                                                                                                                                                                                     |
                Buffers: shared hit=1038 read=205719 dirtied=789                                                                                                                                                                                               |
                I/O Timings: shared/local read=8403.926                                                                                                                                                                                                        |
              Worker 1:  actual time=0.834..19706.338 rows=8261642 loops=1                                                                                                                                                                                     |
                Buffers: shared hit=1024 read=201674 dirtied=982                                                                                                                                                                                               |
                I/O Timings: shared/local read=8412.231                                                                                                                                                                                                        |
        ->  Function Scan on pg_catalog.jsonb_array_elements refno  (cost=0.00..1.00 rows=100 width=32) (actual time=0.000..0.000 rows=1 loops=25056480)                                                                                                     |
              Output: refno.value                                                                                                                                                                                                                            |
              Function Call: jsonb_array_elements('[{"Id": "0001", "Type": "RefType"}]'::jsonb)                                                                                                                                                          |
              Worker 0:  actual time=0.000..0.000 rows=1 loops=8448451                                                                                                                                                                                         |
              Worker 1:  actual time=0.000..0.000 rows=1 loops=8261642                                                                                                                                                                                         |
Settings: effective_cache_size = '2640112kB', search_path = 'public, public, "$user"'                                                                                                                                                                          |
Query Identifier: -5824741316160729696                                                                                                                                                                                                                         |
Planning:                                                                                                                                                                                                                                                      |
  Buffers: shared hit=1                                                                                                                                                                                                                                        |
Planning Time: 0.358 ms                                                                                                                                                                                                                                        |
Execution Time: 42840.510 ms                                                                                                                                                                                                                                   |

解决方案:让查询命中GIN索引

要实现“匹配任意指定RefType对象”且利用GIN索引,可采用以下两种方式:

方式1:使用OR连接独立条件

将每个待匹配的RefType对象作为单独的@>条件,用OR连接,PostgreSQL会自动合并多个索引扫描的结果:

SELECT * FROM refs
WHERE json_data -> 'References' @> '[{"Id":"001","Type":"RefType"}]'
   OR json_data -> 'References' @> '[{"Id":"002","Type":"RefType"}]';

方式2:使用EXISTS子查询

适合动态生成匹配条件的场景,同样能命中GIN索引:

SELECT r.*
FROM refs r
WHERE EXISTS (
    SELECT 1
    FROM jsonb_array_elements(r.json_data -> 'References') ref
    WHERE ref IN (
        '{"Id":"001","Type":"RefType"}'::jsonb,
        '{"Id":"002","Type":"RefType"}'::jsonb
    )
);

原查询无法使用索引的原因

原查询中ANY(array(...))的结构,无法被PostgreSQL优化器关联到GIN索引的jsonb_path_ops算子,因此只能执行全表扫描。而OR连接的独立条件或EXISTS子查询的形式,能让优化器识别到可通过GIN索引快速定位目标行。


内容的提问来源于stack exchange,提问作者user2686282

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 22:09:49