【问题标题】:Postgres - Performance of large jsonb columnPostgres - 大型 jsonb 列的性能
【发布时间】:2017-06-19 01:30:07
【问题描述】:

我们在我们的一个数据库表中使用 Postgres jsonb 类型。表结构如下:

CREATE TABLE T (
  id UUID NOT NULL PRIMARY KEY,
  payload JSONB
);

CREATE INDEX ON T USING gin (payload jsonb_path_ops);

Payload 是一个复杂的 json 字符串。下面是一个例子:

{
    "business": {
        "taxId": "626642071",
        "legalName": "Jon's Deli",
        "phoneNumbers": [
            {
                "phoneType": "Business",
                "telephoneNumber": "8384407555"
            },
            {
                "phoneType": "Work",
                "telephoneNumber": "6032255248"
            }
        ],
        "addresses": [
            {
                "city": "San Francisco",
                "state": "CA",
                "postalCode": "94101",
                "countryCode": "USA",
                "addressLine1": "123 Market St"
            }
        ]
    },
    "stakeholders": [
        {
            "person": {
                "taxId": "540646815",
                "firstName": "GdXFouh",
                "lastName": "IlUAcgCGz",
                "dateOfBirth": "1980-12-11",
                "emailAddress": "jywxsijgix@qaqmlz.com",
                "phoneNumbers": [
                    {
                        "phoneType": "Mobile",
                        "telephoneNumber": "4901371573"
                    }
                ],
                "addresses": [
                    {
                        "city": "San Francisco",
                        "state": "CA",
                        "postalCode": "94101",
                        "countryCode": "USA",
                        "addressLine1": "123 Market St"
                    }
                ]
            }
        }
    ]
}

注意phoneNumbersaddressesstakeholders是数组,这意味着数组中可以有多个元素。

我尝试在表中插入一百万行。 payload 的每个字段都是随机生成的。最初,测试程序运行得非常快。但是插入大约800,000行后,每1000行就卡住了——插入1000行,然后测试程序挂了2分钟,然后又回来插入1000行,...

我们怀疑这是由于大量 jsonb 索引更新造成的。因为单行的索引中有很多字段要更新。我们只是想确认是否有人遇到过同样的问题。


实际上我们不需要索引整个payload 列。仅需要某些字段:business->taxIdbusiness->phoneNumbers-> telephoneNumberstakeholders->person->taxIdstakeholders->person->emailAddress

我尝试了以下两个索引:

CREATE INDEX ON T USING gin ((payload->'business'->'taxId') jsonb_path_ops);
CREATE INDEX ON T USING gin ((payload ->'stakeholders'->'person'->'taxId') jsonb_path_ops);

并运行两条语句:

explain select * from T where payload->'business'->'taxId' @> '"123456789"'; (1)
explain select * from T where payload->'stakeholders'->'person'->'taxId' @> '"123456789"'; (2)

第一条语句是使用索引。但是第二个是进行非常慢的全表扫描。这就是我们转向索引整个payload 列的原因。


欢迎提出任何建议。

顺便说一句,我们使用的是 Postgres 9.5.4。

【问题讨论】:

  • 如果该有效负载要保持一致,您应该考虑将其转换为真正的模式。 Postgres 这样会更有效率。当您不确定结构是什么或结构稀疏时,请保留 JSONB。人员、地址、电话号码、企业和利益相关者就是您的表格。
  • payload 的架构可能会在不久的将来更改。这就是为什么我们将其保存为 json,而不是以标准化的方式进行。

标签: json performance postgresql indexing jsonb


【解决方案1】:

您的查询:

select * from T where payload->'stakeholders'->'person'->'taxId' @> '"123456789"';

不起作用。这是因为“利益相关者”是数组。工作查询是:

select * from T where payload->'stakeholders' @> '[{"person": {"taxId": "54"}}]'::jsonb

但在这种情况下,postgres 可以对所有利益相关者使用使用索引。

                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=1388.08..1425.90 rows=10 width=36) (actual time=1.959..1.959 rows=1 loops=1)
   Recheck Cond: ((payload -> 'stakeholders'::text) @> '[{"person": {"taxId": "54"}}]'::jsonb)
   Heap Blocks: exact=1
   ->  Bitmap Index Scan on t_expr_idx3  (cost=0.00..1388.08 rows=10 width=0) (actual time=1.946..1.946 rows=1 loops=1)
         Index Cond: ((payload -> 'stakeholders'::text) @> '[{"person": {"taxId": "54"}}]'::jsonb)
 Planning time: 0.071 ms
 Execution time: 1.978 ms

为了使用更具体的索引,我使用修改后的方法:How do you create a Postgresql JSONB array in array index?

CREATE OR REPLACE FUNCTION extract_taxids(a_json jsonb).
RETURNS jsonb AS $BODY$
     SELECT jsonb_agg(j) FROM (SELECT jsonb_array_elements(a_json->'stakeholders')->'person'->'taxId' AS j) AS j
$BODY$ LANGUAGE sql IMMUTABLE;
CREATE INDEX ON T USING gin (extract_taxids(payload));

瞧:

EXPLAIN ANALYZE select * from T where extract_taxids(payload) @> '["54"]';

                                                           QUERY PLAN                                                           
--------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=12.08..52.38 rows=10 width=36) (actual time=0.101..0.102 rows=1 loops=1)
   Recheck Cond: (extract_taxids(payload) @> '["54"]'::jsonb)
   Heap Blocks: exact=1
   ->  Bitmap Index Scan on t_extract_taxids_idx  (cost=0.00..12.07 rows=10 width=0) (actual time=0.008..0.008 rows=1 loops=1)
         Index Cond: (extract_taxids(payload) @> '["54"]'::jsonb)
 Planning time: 0.128 ms
 Execution time: 0.117 ms

【讨论】:

  • 我按照你的方式试过了。 explain 显示它正在使用索引,这很好。但是,select 返回的是空结果,这实际上是错误的。我在表中有这一行。
  • 如果我只想索引stakeholder.person.taxId,我该如何创建索引。基本上我不想索引person中的其他字段。
  • 谢谢罗曼!!!我知道这可能有效。但是看起来很复杂。我决定使用另一种方式:创建一个单独的列,它是一个精益 json,仅包含我们想要索引的字段,例如taxId、电子邮件...
  • 没问题。这是你的问题:-)
猜你喜欢
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-18
  • 1970-01-01
  • 1970-01-01
  • 2023-03-16
相关资源
最近更新 更多