带有 btree 索引的 jsonb 列的统计数据不一致

Mad*_*ist 7 postgresql statistics execution-plan index-tuning json

我注意到涉及 jsonb 列的查询的性能在测试时在 VACUUM ANALYZE 运行之间存在显着差异。分析表格后,我似乎随机得到了完全不同的执行计划。

我在这里使用 Postgres 9.6。我的测试设置如下,我将一个键“x”插入到 jsonb 列“params”中,值在 1 到 6 之间,1 是最稀有的值,6 是最常见的值。我还有一个常规的 int 列“single_param”,其中包含用于比较的相同值分布。:

CREATE TABLE test_data (
    id      serial,
    single_param    int,
    params      jsonb
);

INSERT INTO test_data
SELECT 
    generate_series(1, 1000000) AS id, 
    floor(log(random() * 9999999 + 1)) AS single_param,
    json_build_object(
        'x', floor(log(random() * 9999999 + 1))
    ) AS params;

CREATE INDEX idx_test_btree ON test_data (cast(test_data.params->>'x' AS int));
CREATE INDEX idx_test_gin ON test_data USING GIN (params);
CREATE INDEX ON test_data(id)
CREATE INDEX ON test_data(single_param)
Run Code Online (Sandbox Code Playgroud)

我正在测试的查询是对结果进行分页的典型查询,我按 id 排序并将输出限制为前 50 行。

SELECT * FROM test_data where (params->>'x')::int = 1 ORDER BY id DESC LIMIT 50;
Run Code Online (Sandbox Code Playgroud)

运行后,我随机得到两个解释分析输出之一VACUUM ANALYZE

CREATE TABLE test_data (
    id      serial,
    single_param    int,
    params      jsonb
);

INSERT INTO test_data
SELECT 
    generate_series(1, 1000000) AS id, 
    floor(log(random() * 9999999 + 1)) AS single_param,
    json_build_object(
        'x', floor(log(random() * 9999999 + 1))
    ) AS params;

CREATE INDEX idx_test_btree ON test_data (cast(test_data.params->>'x' AS int));
CREATE INDEX idx_test_gin ON test_data USING GIN (params);
CREATE INDEX ON test_data(id)
CREATE INDEX ON test_data(single_param)
Run Code Online (Sandbox Code Playgroud)

或者

SELECT * FROM test_data where (params->>'x')::int = 1 ORDER BY id DESC LIMIT 50;
Run Code Online (Sandbox Code Playgroud)

不同之处在于两个计划之间对匹配 where 子句的列数的估计不同。第一个估计是 2650 行,第二个是 1 行,而实际数字是 10 行。

以下可能使用 GIN 索引的查询版本似乎对 json 列使用了 1% 的默认估计值,这也会导致如上所示的错误查询计划:

Limit  (cost=0.42..836.59 rows=50 width=33) (actual time=39.679..410.292 rows=10 loops=1)
  ->  Index Scan Backward using test_data_id_idx on test_data  (cost=0.42..44317.43 rows=2650 width=33) (actual time=39.678..410.283 rows=10 loops=1)
        Filter: (((params ->> 'x'::text))::integer = 1)
        Rows Removed by Filter: 999990"
Planning time: 0.106 ms
Execution time: 410.314 ms
Run Code Online (Sandbox Code Playgroud)

我最初的假设是 Postgres 不会在 jsonb 列上有任何统计信息,并且总是像使用@>运算符进行查询一样使用估计值。但是对于编写为能够使用我创建的 btree 索引的查询,它使用不同的估计。有时这些已经足够好,有时它们很糟糕。

这些估计来自哪里?我猜它们是 Postgres 使用索引创建的某种统计数据。对于列统计信息,可以选择收集更准确的统计信息,这里有这些统计信息吗?或者任何其他方式让 Postgres 在我的情况下选择更好的计划?

Erw*_*ter 6

目前(9.6 版),Postgres 没有任何关于文档类型(如、、或 )内部的统计信息。(已经讨论过是否以及如何改变它。)相反,Postgres 查询规划器使用恒定的默认频率估计(就像你观察到的那样)。jsonjsonbxmlhstore

但是对于像您的idx_test_btree. 该手册为您提供了以下提示

提示:虽然ANALYZE频率的每列调整可能不是很有成效,但您可能会发现对由 ANALYZE. 在WHERE子句中大量使用且具有高度不规则数据分布的列可能需要比其他列更细粒度的数据直方图。请参阅ALTER TABLE SET STATISTICS,或使用 default_statistics_target 配置参数更改数据库范围的默认值。

此外,默认情况下,关于函数选择性的可用信息有限。但是,如果创建使用函数调用的表达式索引,则会收集有关该函数的有用统计信息,这可以大大改进使用表达式索引的查询计划。

收集的统计数据量取决于一般设置 default_statistics_target,这可以被每列设置否决。列的设置自动涵盖依赖索引。

的默认设置100是保守的。对于 1M 行的测试,如果数据分布不均匀,则可能有助于大幅增加它。检查在这一次,我发现你其实可以调整的每个索引列的统计指标ALTER INDEX,这是目前没有记录。请参阅有关 pgsql-docs 的相关讨论。

ALTER TABLE idx_test_btree ALTER int4 SET STATISTICS 2000;  -- max 10000, default 100
Run Code Online (Sandbox Code Playgroud)

索引列的默认名称并不完全直观,但您可以通过以下方式查找:

SELECT attname FROM pg_attribute WHERE attrelid = 'idx_test_btree'::regclass
Run Code Online (Sandbox Code Playgroud)

应该导致类型名称int4作为您的案例的索引列名称。

的最佳设置STATISTICS取决于几个因素:数据分布、数据类型、更新频率、典型查询的特征、...

在内部,这设置了 的值pg_attribute.attstattarget,其确切含义是(根据文档):

对于标量数据类型,attstattarget既是要收集的“最常见值”的目标数量,也是要创建的直方图箱的目标数量。

然后运行ANALYZE,如果你不想等待自动清理在踢:

ANALYZE test_data;
Run Code Online (Sandbox Code Playgroud)

您必须ANALYZE使用表,因为您不能ANALYZE直接索引。检查(前后如果要验证效果):

SELECT * FROM pg_statistic WHERE starelid = 'idx_test_btree'::regclass;
Run Code Online (Sandbox Code Playgroud)

再次尝试您的查询...

有关的: