Chris van Eijk:PostgreSQL 中 hstore 与 jsonb 的性能实测对比

Chris van Eijk:PostgreSQL 中 hstore 与 jsonb 的性能实测对比

💡 原文英文,约2000词,阅读约需7分钟。
📝

内容提要

文章比较了PostgreSQL中hstore与jsonb处理扁平数据的性能。百万行测试显示,hstore在读取、加载和更新上更快,存储占用与全字符串jsonb相同;但hstore所有值均为文本,会丢失类型,排序和转换需额外处理。GIN索引在PG17中帮助有限,PG18明显改善。建议扁平文本属性用hstore,含数值、嵌套或需JSON输出用jsonb,包含查询索引选jsonb_path_ops。

🔎

延伸解读

存储差异源于类型而非格式

实测中,全字符串jsonb与hstore的存储占用完全相同(221.5字节/行),而带类型的jsonb多出18字节/行。这说明hstore的体积优势并非来自格式本身,而是因为它把所有值都存为文本。若数据原本就是文本,hstore不会额外节省空间;若需要保留数值类型,jsonb的额外开销很小。

GIN索引效果高度依赖PG版本

在PG17上,对匹配约1%行的查询,GIN索引几乎无帮助,hstore甚至比不用索引更慢;PG18上所有索引都带来明显加速。文章指出规划器对结果行数的估计严重偏低,导致索引路径选择不佳。因此,评估索引收益时不能只看类型,必须结合具体PG版本和查询选择性。

hstore的类型丢失影响排序与转换

hstore所有值均为文本,数值排序会按字符串比较(如'9999'大于'10000'),需显式转型。转回JSON时,hstore_to_jsonb_loose只能猜测类型,且会漏判'true'/'false'和带前导零的数字。若数据需以JSON输出或参与数值计算,jsonb保留类型信息更可靠。

索引选型:jsonb_path_ops更小更快但有代价

jsonb_path_ops索引比jsonb_ops小三分之一,构建时间不到一半,查询也最快,但不支持键存在操作符?,相关查询会退化为顺序扫描。文章建议,若只需包含查询且不需要?,优先选jsonb_path_ops;若需检查键是否存在,则只能使用jsonb_ops。

❓

Q&A

在PostgreSQL中处理扁平数据,hstore和jsonb哪个读取速度更快?

对于扁平数据,hstore在读取上通常更快。在PostgreSQL 17.11的百万行测试中,读取一个键(color = 'red')hstore耗时58.7毫秒,jsonb耗时67.9毫秒;包含查询(colour和size)hstore为48.1毫秒,jsonb为84.5毫秒。在PostgreSQL 18.6上顺序相同,hstore仍更快。

hstore和jsonb在存储空间上有什么区别?

存储空间差异主要来自类型而非格式。测试中,hstore的平均值大小为221.5字节,带类型的jsonb为239.5字节,而所有值均为字符串的jsonb与hstore完全相同(221.5字节)。带主键的表大小:hstore为287.0 MB,带类型jsonb为305.1 MB,全字符串jsonb为287.0 MB。

使用hstore会丢失哪些数据类型?会带来什么影响?

hstore的所有值都存储为文本,因此会丢失原始类型信息。例如,数字和布尔值在hstore中都是字符串,排序时按文本比较(如'9999'大于'10000'),需要显式转换才能按数值处理;转换为JSON时所有值都变成字符串,且hstore_to_jsonb_loose在转换时对数字和布尔值的识别并不可靠(如true/false仍为字符串,042被保留为字符串)。

在PostgreSQL 17和18中,GIN索引对hstore和jsonb查询的帮助有多大?

在PostgreSQL 17上,对于匹配百分之一行的查询,GIN索引帮助有限甚至可能变慢:hstore使用GIN索引后耗时67.2毫秒,反而比无索引的48.1毫秒更慢;jsonb使用jsonb_path_ops索引为57.8毫秒,略快于无索引的84.5毫秒。在PostgreSQL 18.6上,所有索引都带来了提升:hstore使用GIN为46.5毫秒(无索引66.4毫秒),jsonb_path_ops为39.5毫秒(无索引96.0毫秒)。

什么场景下应该使用hstore而不是jsonb?

适合使用hstore的场景:扁平的文本属性,经常写入并按key读取,例如标签、标签、每个租户的设置。在这些场景中,hstore与存储字符串值的jsonb占用空间相同,加载时间减半,扫描更快,更新速度相当,且类型丢失没有代价,因为值本来就是文本。对于审计触发器中变更的列,hstore也是最快的选项(但需注意时间戳的文本格式问题)。

如果需要对包含查询建立索引,应该选择哪种jsonb的GIN操作符类?

对于包含查询,推荐使用jsonb_path_ops操作符类。测试中,jsonb_path_ops索引构建时间最短(8.7秒),索引大小最小(148.2 MB),查询速度最快(57.8毫秒)。但它不支持键存在操作符?,如果需要该操作符,则需使用jsonb_ops。

🏷️

标签

➡️

继续阅读