使用顺序扫描而不是索引扫描的SELECT

时间:2011-06-03 12:32:53

标签: sql postgresql

我正在尝试使用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

5 个答案:

答案 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