Postgres在查询索引表达式视图时使用了错误的索引?

时间:2014-01-16 02:58:11

标签: postgresql

当我使用Postgres 9.3(enable_seqscan设置为off)运行以下脚本时,我希望最终查询使用“forms_string”部分索引,而是使用“forms_int” “索引,没有意义。

当我使用具有JSON函数和索引的实际代码对更多类型进行测试时,对于每个查询,它始终似乎使用最后创建的索引。

添加更多不相关的行,以便与部分索引相关的行只占表中总行数的一小部分,从而导致“位图堆扫描”,但在此之后仍然会提到相同的错误索引。

知道我怎么能让它使用正确的索引吗?

CREATE EXTENSION IF NOT EXISTS plv8;

CREATE OR REPLACE FUNCTION json_string(data json, key text) RETURNS TEXT AS $$ var ret = data, keys = key.split('.'), len = keys.length; for (var i = 0; i < len; ++i) { if (ret) { ret = ret[keys[i]] }; } if (typeof ret === "undefined") { ret = null; } else if (ret) { ret = ret.toString(); } return ret;

$$ LANGUAGE plv8 IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION json_int(data json, key text) RETURNS INT AS $$ var ret = data, keys = key.split('.'), len = keys.length; for (var i = 0; i < len; ++i) { if (ret) { ret = ret[keys[i]] } } if (typeof ret === "undefined") { ret = null; } else { ret = parseInt(ret, 10); if (isNaN(ret)) { ret = null; } } return ret; $$ LANGUAGE plv8 IMMUTABLE STRICT;

CREATE TABLE form_types ( id SERIAL NOT NULL, name VARCHAR(200), PRIMARY KEY (id) );

CREATE TABLE tenants ( id SERIAL NOT NULL, name VARCHAR(200), PRIMARY KEY (id) );

CREATE TABLE forms ( id SERIAL NOT NULL, tenant_id INTEGER, type_id INTEGER, data JSON, PRIMARY KEY (id), FOREIGN KEY(tenant_id) REFERENCES tenants (id), FOREIGN KEY(type_id) REFERENCES form_types (id) );

CREATE INDEX ix_forms_type_id ON forms (type_id); CREATE INDEX ix_forms_tenant_id ON forms (tenant_id); INSERT INTO tenants (name) VALUES ('mike'), ('bob'); INSERT INTO form_types (name) VALUES ('type 1'), ('type 2'); INSERT INTO forms (tenant_id, type_id, data) VALUES (1, 1, '{"string": "unicorns", "int": 1}'), (1, 1, '{"string": "pythons", "int": 2}'), (1, 1, '{"string": "pythons", "int": 8}'), (1, 1, '{"string": "penguins"}');

CREATE OR REPLACE VIEW foo AS SELECT forms.id AS forms_id, json_string(forms.data, 'string') AS "data.string", json_int(forms.data, 'int') AS "data.int" FROM forms WHERE forms.tenant_id = 1 AND forms.type_id = 1;

CREATE INDEX "forms_string" ON forms (json_string(data, 'string')) WHERE tenant_id = 1 AND type_id = 1; CREATE INDEX "forms_int" ON forms (json_int(data, 'int')) WHERE tenant_id = 1 AND type_id = 1;

EXPLAIN ANALYZE VERBOSE SELECT "data.string" from foo;

输出:

 Index Scan using forms_int on public.forms
(cost=0.13..8.40 rows=1 width=32) (actual time=0.085..0.239 rows=20 loops=1) Output: json_string(forms.data, 'string'::text) Total runtime: 0.282 ms

没有enable_seqscan=off

Seq Scan on public.forms  (cost=0.00..1.31 rows=1 width=32) (actual time=0.080..0.277 rows=28 loops=1)
   Output: json_string(forms.data, 'string'::text)
   Filter: ((forms.tenant_id = 1) AND (forms.type_id = 1))
 Total runtime: 0.327 ms

\d forms打印

                           Table "public.forms"
  Column   |  Type   |                     Modifiers
-----------+---------+---------------------------------------------------- id | integer | not null default nextval('forms_id_seq'::regclass) tenant_id | integer | type_id | integer | data | json | Indexes: "forms_pkey" PRIMARY KEY, btree (id) "forms_int" btree (json_int(data, 'int'::text)) WHERE tenant_id = 1 AND type_id = 1 "forms_string" btree (json_string(data, 'string'::text)) WHERE tenant_id = 1 AND type_id = 1 "ix_forms_tenant_id" btree (tenant_id) "ix_forms_type_id" btree (type_id) Foreign-key constraints: "forms_tenant_id_fkey" FOREIGN KEY (tenant_id) REFERENCES tenants(id) "forms_type_id_fkey" FOREIGN KEY (type_id) REFERENCES form_types(id)

2 个答案:

答案 0 :(得分:3)

索引与seqscan,成本

与您的计算机的实际性能相比,您的random_page_cost看起来太高了。随机I / O比Pg认为的更快(成本更低),所以它选择了一个稍微不太理想的计划。

这就是为什么indexscan的成本估算为(cost=0.13..8.40 rows=1 width=32),而seqscan的成本估算在(cost=0.00..1.31 rows=1 width=32)处略低。

降低random_page_cost - 尝试SET random_page_cost = 2然后重新开始运行。

要了解更多信息,请阅读有关PostgreSQL查询规划,参数和调优以及相关Wiki页面的文档。

索引选择

PostgreSQL似乎正在forms_int而不是forms_string上选择索引扫描,因为它将是一个更紧凑,更小的索引,并且两个索引都与视图的搜索条件完全匹配:{{ 1}}。

如果您停用或删除tenant_id = 1 AND type_id = 1,它可能会使用forms_int并稍微放慢一点。

要理解的关键是,虽然索引确实包含了感兴趣的值,但PostgreSQL实际上并没有使用它。它正在扫描索引而没有索引条件,因为索引中的每个元组都匹配,以从堆中获取元组。然后它从那些堆元组中提取值并输出它们。

这可以通过常量上的表达式索引来证明:

forms_string

PostgreSQL很可能为查询选择此索引:

CREATE INDEX "forms_novalue" ON forms((true)) WHERE tenant_id = 1 AND type_id = 1;

所有索引的大小都相同,因为它们都非常小,符合最小分配:

regress=# EXPLAIN ANALYZE VERBOSE SELECT "data.string" from foo;
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Index Scan using forms_novalue on public.forms  (cost=0.13..13.21 rows=4 width=32) (actual time=0.190..0.310 rows=4 loops=1)
   Output: json_string(forms.data, 'string'::text)
 Total runtime: 0.346 ms
(3 rows)

但由于行宽较窄,novalue的统计数据会更具吸引力。

索引扫描与仅索引扫描

听起来你真正期望的是一个index- only 扫描,其中Pg从不接触表的堆,只使用索引中的元组。

我希望regress=# SELECT x.idxname, pg_relation_size(x.idxname) FROM (VALUES ('forms_novalue'),('forms_int'),('forms_string')) x(idxname); idxname | pg_relation_size ---------------+------------------ forms_novalue | 16384 forms_int | 16384 forms_string | 16384 (3 rows) 可以满足此查询的要求,但无法让Pg为其选择仅索引扫描计划。

我不能立即清楚为什么Pg在这里不使用仅索引扫描,因为它应该是一个候选者,但它似乎无法计划一个。如果我强制forms_string,它会选择一个较差的位图索引扫描计划,如果强制禁用enable_indexscan = off,它将回退到最大成本估算seqscan。即使在感兴趣的表enable_bitmapscan之后也是如此。

这意味着它不能被生成为查询规划器中的候选路径 - Pg不知道如何对此查询使用仅索引扫描,或者认为由于某种原因它不能这样做。

视图内省不是问题,因为扩展视图查询是相同的。

答案 1 :(得分:2)

您的表格中的数据不足。简而言之,当表格适合单个磁盘页面时,Postgres不会使用索引。永远。当你的表包含几百或几千行时,它会变得太大而不适合,然后你会看到Postgres在相关时开始使用索引扫描。

需要考虑的另一点是,在进行大量导入后,您需要analyze表。如果没有关于实际数据的准确统计数据,Postgres最终可能会将某些索引扫描视为过于昂贵,而实际上它们并不便宜。

最后,有些情况 实质上,只要Postgres要重复访问大多数磁盘页面并以随机顺序访问大量行,它就会认真考虑访问大多数(位图索引)或所有(seq扫描)磁盘页面的成本一次顺序并过滤掉无效的行。如果你选择了足够的行,后者就会获胜。