如何访问 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)
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 操作,并非针对每一行)。
在大数据查询中,中介 "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)
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 操作,并非针对每一行)。