簡體   English   中英

SELECT 使用順序掃描而不是索引掃描

[英]SELECT that uses sequential scan instead of index scan

我正在嘗試使用解釋分析優化我的一些選擇,但我不明白為什么 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

如果您希望 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 >= '6' AND compte < '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

在您的情況下,此解決方案可能更好。

似乎 DBMS 正在估計,關於同意的 JOIN 將比過濾競爭,然后加入更具限制性。

選項可能是...
1. 在assentaments.compte_id上放置一個索引
2. 將您在comptes上的索引更改為包含id作為第一個索引字段。


第一個選項可能允許執行計划反轉:過濾競爭,然后加入同意。

第二個選項可能允許執行計划保持不變,但允許使用索引。

這通常是由於索引上的錯誤統計信息造成的,即如果索引沒有足夠的選擇性(例如,許多重復值),則訪問和過濾索引可能比進行 seq 掃描更耗時。

您對c.codi_compte的價值觀是否足夠有選擇性? 也許您的 null 值太多?

我會嘗試

  • 表協議上的復合索引(data, compte_id) assentaments

  • comptes上的復合索引(empresa_id, nivell, codi_compte, id)

您還應該將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%'

這意味着無法使用索引。 如果是這種情況,您可以將字段轉換為 char 類型。

您可以/應該做一些事情。 第一的:

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

在“public”上創建索引“index_multiple”。“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會更快地加入 bitmap 或 hash 加入。 問題的症結在於 PostgreSQL (可能正確)認為執行index_scan比執行seq_scan更昂貴。

    AND (a.data >='2007-01-01' AND a.data <='2007-01-31')
    AND c.codi_compte LIKE '6%';

a.data上的INDEX也可能會有所幫助,因為 PostgreSQL 可能會根據assentaments表中的行數在給定日期執行index_scan

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM