优化具有非空约束的 Postgres JSONB 查询

ada*_*amc 2 postgresql jsonb

我有一个 Postgres 9.4.4 数据库,其中包含 170 万条记录,以下信息存储在名为 的表中调用的 JSONB 列dataaccounts

data: {
  "lastUpdatedTime": "2016-12-26T12:09:43.901Z",
  "UID": "2c5bb7fd-1a00-4988-8d92-ffaa52ebc20d",
  "data": {
    "country": "UK",
    "verified_at": "2017-01-01T23:49:10.217Z"
  }
}
Run Code Online (Sandbox Code Playgroud)

数据格式无法更改,因为这是旧信息。

我需要获取国家/地区所在的所有帐户UK,该verified_at值不为空并且该lastUpdatedTime值大于某个给定值。

到目前为止,我有以下查询:

SELECT * FROM "accounts"
WHERE (data @> '{ "data": { "country": "UK" } }')
AND (data->'data' ? 'verified_at')
AND ((data->'data' ->> 'verified_at') is not null)
AND (data ->>'lastUpdatedTime' > '2016-02-28T05:49:08.511846')
ORDER BY data ->>'lastUpdatedTime' LIMIT 100 OFFSET 0;
Run Code Online (Sandbox Code Playgroud)

以及以下指标:

"accounts_idxgin" gin (data)
"accounts_idxgin_on_data" gin ((data -> 'data'::text))
Run Code Online (Sandbox Code Playgroud)

我已经设法将查询时间降低到大约 1000 到 4000 毫秒

以下是查询的分析:

 Bitmap Heap Scan on accounts  (cost=41.31..6934.50 rows=9 width=1719)
                               (actual time=7.273..1067.657 rows=23190 loops=1)
   Recheck Cond: ((data -> 'data'::text) ? 'verified_at'::text)
   Filter: ((((data -> 'data'::text) ->> 'verified_at'::text) IS NOT NULL)
           AND ((data ->> 'lastUpdatedTime'::text) > '2016-02-01 05:49:08.511846'::text)
           AND (((data -> 'data'::text) ->> 'country'::text) = 'UK'::text))
   Rows Removed by Filter: 4
   Heap Blocks: exact=16039
   ->  Bitmap Index Scan on accounts_idxgin_on_data  (cost=0.00..41.30 rows=1773 width=0)
       (actual time=4.618..4.618 rows=23194 loops=1)
         Index Cond: ((data -> 'data'::text) ? 'verified_at'::text)
 Planning time: 0.448 ms
 Execution time: 1069.344 ms
(9 rows)
Run Code Online (Sandbox Code Playgroud)

我有以下问题

  1. 我可以做些什么来进一步加快这个查询的速度吗?
  2. field is not null使用 JSONB加速查询的正确方法是什么?我最终使用存在运算符 with(data->'data' ? 'verified_at')来过滤掉大量不匹配的记录,因为我的大部分数据都没有verified_at顶级键。这提高了查询的速度,但我想知道是否有一种通用方法来优化此类查询。
  3. 为了使用存在运算符(data->'data' ? 'verified_at'),我需要在 上添加另一个索引((data -> 'data'::text))。我已经在 上有一个索引gin (data),但存在运算符没有使用它。这是为什么?我认为存在和包含运算符会使用这个索引。

poz*_*ozs 5

3:不是真的。文档中明确提到了这种情况。当您在列上有索引时data,只有在查询表时才使用它,例如data @> '...'data ? '...'。当表达式上有索引时(data -> 'data'),这些查询可以利用它:(data -> 'data') @> '...'(data -> 'data') ? '...'

2:通常的jsonb索引在查询期间根本没有帮助(jsonb_col -> '<key>') is [not] null。不幸的是,您不能使用jsonb_col @> '{"<key>":null}'其中任何一个,因为 JSON 对象可能完全缺少密钥。反向使用索引(for is not null)也是根本不可能的。但也许有一个技巧...

1:不多。可能会有一些改进,但不要指望有巨大的性能优势。所以他们来了:

您可以使用jsonb_path_ops运算符类代替(默认)jsonb_ops。这应该意味着性能上有一点改进,但​​他们不能使用存在运算符(?)。但无论如何我们都不需要它。

您有一个单一的、索引不友好的boolean类型化表达式,这会减慢您的速度。值得庆幸的是,如果您只对值感兴趣,您可以在此处使用部分索引true

所以,你的索引应该是这样的:

create index accounts_idxgin_on_data
  on accounts using gin ((data -> 'data') jsonb_path_ops)
  where (data -> 'data' ->> 'verified_at') is not null;
Run Code Online (Sandbox Code Playgroud)

有了这个索引,您可以使用以下查询:

select   *
from     accounts
where    (data -> 'data') @> '{"country":"UK"}'
and      (data -> 'data' ->> 'verified_at') is not null
and      (data ->> 'lastUpdatedTime') > '2016-02-28T05:49:08.511Z'
order by data ->>'lastUpdatedTime';
Run Code Online (Sandbox Code Playgroud)

注意:为了正确timestamp比较,您应该使用(data ->> 'lastUpdatedTime')::timestamptz > '2016-02-28T05:49:08.511Z'.

http://rextester.com/QWUW41874