我注意到涉及 jsonb 列的查询的性能在 VACUUM ANALYZE 运行期间差异很大,同时测试它。在分析表格后,我似乎随机地得到了完全不同的执行计划。
我在这里使用 Postgres 9.6。我的测试设置如下,我在 jsonb 列“params”中插入一个键“x”,其值介于 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)
我正在测试的查询是用于分页结果的典型查询,我按 id 排序并将输出限制为前 50 行。
SELECT * FROM test_data where (params->>'x')::int = 1 ORDER BY id DESC LIMIT 50;
运行后,我随机得到两个解释分析输出之一VACUUM ANALYZE
:
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
或者
Limit (cost=8.45..8.46 rows=1 width=33) (actual time=0.032..0.034 rows=10 loops=1)
-> Sort (cost=8.45..8.46 rows=1 width=33) (actual time=0.032..0.032 rows=10 loops=1)
Sort Key: id DESC
Sort Method: quicksort Memory: 25kB
-> Index Scan using idx_test_btree on test_data (cost=0.42..8.44 rows=1 width=33) (actual time=0.007..0.016 rows=10 loops=1)
Index Cond: (((params ->> 'x'::text))::integer = 1)
Planning time: 0.320 ms
Execution time: 0.052 ms
不同之处在于,与 where 子句匹配的列数的估计值在两个计划之间是不同的。第一个估计是 2650 行,第二个是 1 行,而实际数字是 10 行。
以下可能使用 GIN 索引的查询版本似乎对 json 列使用了 1% 的默认估计值,这也导致了上述错误的查询计划:
SELECT * FROM test_data where params @> '{"x": 1}' ORDER BY id DESC LIMIT 50;
我最初的假设是 Postgres 不会对 jsonb 列有任何统计信息,并且总是使用估计值,就像使用@>
运算符进行查询一样。但是对于为能够使用我创建的 btree 索引而编写的查询,它使用不同的估计值。有时这些足够好,有时它们很糟糕。
这些估计来自哪里?我猜它们是 Postgres 使用索引创建的某种统计信息。对于列统计信息,可以选择收集更准确的统计信息,这些统计信息有类似的吗?或者任何其他方式让 Postgres 在我的情况下选择更好的计划?