如何访问 JSONb 的内部表示?

How to access internal representation of JSONb?

在大数据查询中,中介 "CAST to text" 是一个性能瓶颈...好的二进制信息就在那里,在 JSONb 数据类型:如何拯救它?

典型"select where"例子:

 with t(x,j) as (select 'hello','{"flag1":true,"flag2":false}'::jsonb) 
 SELECT x FROM t 
 WHERE (j->>'flag1')::boolean AND NOT((j->>'flag2')::boolean)

"casting to text"性能损失很大。理想是一种直接从 JSONb 到 Boolean 的机制,如

 WHERE (j->'flag1')::magic_boolean AND NOT((j->'flag2')::magic_boolean)

PS: 可以用C++吗? CREATE CAST C++ 实现可以解决这个问题吗?

该功能已在 Postgres 11:

中实现

E.4.3.4. Data Types

[...]

Add casts from JSONB scalars to numeric and boolean data types (Anastasia Lubennikova)

Db<>Fiddle.

TL;DR

Performance-wise 最好将 #> 与涵盖所有 JSON 属性的适当索引一起使用,包括类型转换(以避免在访问索引时进行类型转换):https://dbfiddle.uk/?rdbms=postgres_11&fiddle=4da77576874651f4d2cf801142ae34d2

CREATE INDEX idx_flags_btree_jsonb ON t ((j#>'{flag1}'), (j#>'{flag2}'));

次(全部从 1,000,000 行中选择相同的 5,195 行):

->>::boolean | ~75 ms
->::boolean  | ~55 ms
@>           | ~80 ms
#>           | ~40 ms

可扩展性:

有趣的是,一个有 40M 行的本地测试(全部缓存在内存中,这里没有 I/O 延迟)揭示了 10 运行 中的以下(最佳)数字(不包括第一个和最后一个) 运行) 每个查询:

->>::boolean |  222.333 ms
->::boolean  |  268.002 ms
@>           | 1644.605 ms
#>           |  207.230 ms

所以,事实上,new cast 似乎在更大的数据集上放慢了速度(我怀疑这是因为它仍然转换为 text 在转换为 boolean 之前,但在包装器中,而不是直接转换为 boolean

我们还可以看到,使用 GIN 索引的 @> 运算符在这里的扩展性不是很好,这是预料之中的,因为它比其他运算符更通用 special-purpose 索引,因此需要做更多 under-the-hood.

然而,如果这些特殊用途的 btree 索引无法到位或 I/O 成为瓶颈,那么 GIN 索引将更胜一筹,因为它只消耗一小部分space 在磁盘上(以及在内存中),增加索引缓冲区命中的机会。

但这取决于很多因素,需要在了解所有访问应用程序的情况下做出决定。

详情:

最好对单个 GIN 索引使用 @> 包含运算符,因为它可以节省大量 special-purpose 索引:

with t(x,j) as (select 'hello','{"flag1":true,"flag2":false}'::jsonb) 
 SELECT x FROM t 
 WHERE j @> '{"flag1":true, "flag2":false}'::jsonb;

...给出的计划如下:

                        QUERY PLAN                         
-----------------------------------------------------------
 CTE Scan on t  (cost=0.01..0.03 rows=1 width=32)
   Filter: (j @> '{"flag1": true, "flag2": false}'::jsonb)
   CTE t
     ->  Result  (cost=0.00..0.01 rows=1 width=64)
(4 rows)

作为替代方案(如果你能负担得起创建 special-purpose 索引和由此产生的写惩罚)使用 #> 运算符而不是 ->->> 并且通过那个跳过任何 performance-costly 类型转换,例如

with t(x,j) as (select 'hello','{"flag1":true,"flag2":false}'::jsonb) 
 SELECT x FROM t 
 WHERE j#>'{flag1}' = 'true'::jsonb AND j#>'{flag2}' = 'false'::jsonb;

...制定如下计划:

                                               QUERY PLAN                                               
--------------------------------------------------------------------------------------------------------
 CTE Scan on t  (cost=0.01..0.04 rows=1 width=32)
   Filter: (((j #> '{flag1}'::text[]) = 'true'::jsonb) AND ((j #> '{flag2}'::text[]) = 'false'::jsonb))
   CTE t
     ->  Result  (cost=0.00..0.01 rows=1 width=64)
(4 rows)

因此,这里不再进行隐式类型转换(仅针对给定常量,但这是一个 one-time 操作,并非针对每一行)。