我正在尝试使用explain analyze优化我的一些选择,我无法理解为什么postgresql使用序列扫描而不是索引扫描:
explain analyze SELECT SUM(a.deure)-SUM(a.haver) as Value FROM assentaments a
LEFT JOIN comptes c ON a.compte_id = c.id WHERE c.empresa_id=2 AND c.nivell=11 AND
(a.data >='2007-01-01' AND a.data <='2007-01-31') AND c.codi_compte LIKE '6%';
------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=44250.26..44250.27 rows=1 width=12)
(actual time=334.054..334.054 rows=1 loops=1)
-> Nested Loop (cost=0.00..44249.20 rows=211 width=12)
(actual time=65.277..333.179 rows=713 loops=1)
-> Seq Scan on comptes c (cost=0.00..8001.72 rows=118 width=4)
(actual time=0.053..64.287 rows=236 loops=1)
Filter: (((codi_compte)::text ~~ '6%'::text) AND
(empresa_id = 2) AND (nivell = 11))
-> Index Scan using index_compte_id on assentaments a
(cost=0.00..307.16 rows=2 width=16) (actual time=0.457..1.138 rows=3 loops=236)
Index Cond: (a.compte_id = c.id)
Filter: ((a.data >= '2007-01-01'::date) AND (a.data <= '2007-01-31'::date))
Total runtime: 334.104 ms
(8 rows)
我已经创建了一个自定义索引:
CREATE INDEX "index_multiple" ON "public"."comptes" USING btree(codi_compte ASC NULLS LAST,
empresa_id ASC NULLS LAST, nivell ASC NULLS LAST);
而且我在comptes表上为这三个字段创建了三个新索引,只是为了检查它是否需要索引扫描,但结果是相同的:
CREATE INDEX "index_codi_compte" ON "public"."comptes" USING btree(codi_compte ASC NULLS LAST);
CREATE INDEX "index_comptes" ON "public"."comptes" USING btree(codi_compte ASC NULLS LAST);
CREATE INDEX "index_multiple" ON "public"."comptes" USING btree(codi_compte ASC NULLS LAST, empresa_id ASC NULLS LAST, nivell ASC NULLS LAST);
CREATE INDEX "index_nivell" ON "public"."comptes" USING btree(nivell ASC NULLS LAST);
谢谢!
米。
assentaments.id和assentaments.data的索引也是
select count(*) FROM comptes => 148498
select count(*) from assentaments => 2128771
select count(distinct(codi_compte)) FROM comptes => 137008
select count(distinct(codi_compte)) FROM comptes WHERE codi_compte LIKE '6%' => 368
select count(distinct(codi_compte)) FROM comptes WHERE codi_compte LIKE '6%' AND empresa_id=2; => 303
答案 0 :(得分:6)
如果您希望TEXT上的索引索引LIKE查询,则需要使用text_pattern_ops创建它,如下所示:
test=> CREATE TABLE t AS SELECT n::TEXT FROM generate_series( 1,100000 ) n;
test=> CREATE INDEX tn ON t(n);
test=> VACUUM ANALYZE t;
test=> EXPLAIN ANALYZE SELECT * FROM t WHERE n LIKE '123%';
QUERY PLAN
--------------------------------------------------------------------------------------------------
Seq Scan on t (cost=0.00..1693.00 rows=10 width=5) (actual time=0.027..14.631 rows=111 loops=1)
Filter: (n ~~ '123%'::text)
Total runtime: 14.664 ms
test=> CREATE INDEX tn2 ON t(n text_pattern_ops);
CREATE INDEX
Temps : 267,589 ms
test=> EXPLAIN ANALYZE SELECT * FROM t WHERE n LIKE '123%';
QUERY PLAN
---------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on t (cost=5.25..244.79 rows=10 width=5) (actual time=0.089..0.121 rows=111 loops=1)
Filter: (n ~~ '123%'::text)
-> Bitmap Index Scan on tn2 (cost=0.00..5.25 rows=99 width=0) (actual time=0.077..0.077 rows=111 loops=1)
Index Cond: ((n ~>=~ '123'::text) AND (n ~<~ '124'::text))
Total runtime: 0.158 ms
在此处查看详细信息:
http://www.postgresql.org/docs/9.1/static/indexes-opclass.html
如果您不想创建其他索引,并且列是TEXT,则可以将“compte LIKE'6%'”替换为“compte&gt; ='6'AND compte&lt;'7'”,这是一个简单的指数范围条件。
test=> EXPLAIN ANALYZE SELECT * FROM t WHERE n >= '123' AND n < '124';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
Index Scan using tn on t (cost=0.00..126.74 rows=99 width=5) (actual time=0.030..0.127 rows=111 loops=1)
Index Cond: ((n >= '123'::text) AND (n < '124'::text))
Total runtime: 0.153 ms
在你的情况下,这个解决方案可能更好。
答案 1 :(得分:2)
似乎DBMS估计,对于assentaments的JOIN比过滤comptes,然后加入更加严格。
选项可以是......
1.在assentaments.compte_id
上加上一个索引
2.将comptes
上的索引更改为包含id
作为第一个索引字段。
第一个选项可能允许执行计划反转:过滤器压缩,然后加入assentaments。
第二个选项可能允许执行计划保持不变,但允许使用索引。
答案 2 :(得分:0)
这通常是由于索引上的错误统计信息,即如果索引没有足够的选择性(例如,许多重复值),访问和过滤索引可能比执行seq扫描更耗时。 / p>
c.codi_compte
的值是否足够有选择性?也许你有太多的空值?
答案 3 :(得分:0)
我会尝试
表(data, compte_id)
上的复合索引assentaments
和
表(empresa_id, nivell, codi_compte, id)
comptes
您还应该将LEFT JOIN
转换为INNER JOIN
。您拥有的WHERE
条件会使它们等效。也许查询规划器不知道它。
另一个怀疑是字段comptes.codi_compte
的类型。如果是integer
而不是char()
,那么
WHERE c.codi_compte LIKE '6%'
翻译为:
WHERE CAST(c.codi_compte AS CHAR) LIKE '6%'
表示无法使用索引。如果是这种情况,您可以将字段转换为字符类型。
答案 4 :(得分:0)
您可以/应该做一些事情。第一:
SELECT SUM(a.deure)-SUM(a.haver) as Value
SUM()
会触及匹配的每一行......无法INDEX
该操作。
FROM assentaments a, comptes c
调试查询时,我发现使用自然JOIN
而不是显式JOIN
更容易。查询规划器可以更多地释放,并且经常是更好的选择。然而,这不是这里的情况,只是一般性评论。这是INDEX
es与您的查询之间可能存在不匹配的地方。
WHERE TRUE = TRUE
AND a.compte_id = c.id
AND c.empresa_id = 2
AND c.nivell = 11
在这三个查询中,您有以下INDEX
:
CREATE INDEX“index_multiple”ON“public”。“comptes”使用btree(codi_compte ASC NULLS LAST,empresa_id ASC NULLS LAST,nivell ASC NULLS LAST);
由于这不是UNIQUE INDEX
,所以要分开,你不应该看到数据完整性发生任何变化。我之所以提出这个原因,是因为我认为codi_compte
基数较低。我猜empresa_id
会有更高的基数。通常,将您的INDEX
es从最高基数创建为最低。
我怀疑三个INDEX
es会更快地进行位图连接或散列连接。问题的关键在于PostgreSQL(可能是正确的)认为执行index_scan
比执行seq_scan
更昂贵。
AND (a.data >='2007-01-01' AND a.data <='2007-01-31')
AND c.codi_compte LIKE '6%';
INDEX
上的a.data
也可能有用,因为根据index_scan
表格中的行数,PostgreSQL可能会在给定的日期执行assentaments
。< / p>
CREATE INDEX "index_codi_compte" ON "public"."comptes" USING btree(codi_compte ASC NULLS LAST);
CREATE INDEX "index_comptes" ON "public"."comptes" USING btree(codi_compte ASC NULLS LAST);
我不知道为什么你有两次INDEX
。
CREATE INDEX "index_multiple" ON "public"."comptes" USING btree(codi_compte ASC NULLS LAST, empresa_id ASC NULLS LAST, nivell ASC NULLS LAST);
如上所述,打破INDEX
分开。
CREATE INDEX "index_nivell" ON "public"."comptes" USING btree(nivell ASC NULLS LAST);
那INDEX
没问题。
快速提示:
SELECT matching, total, matching / total AS "Want this to be a small number"
FROM
(SELECT count(*)::FLOAT AS matching FROM tbl WHERE col_id = 1) AS matching,
(SELECT count(*)::FLOAT AS total FROM tbl) AS total;
matching rows | total rows | want this to be a small number
---------------+------------+--------------------------------
1 | 10 | 0.1
(1 row)
第三列理想地等于1/total
。