SELECT, который использует последовательное сканирование вместо сканирования индекса - PullRequest
12 голосов
/ 03 июня 2011

Я пытаюсь оптимизировать некоторые из моих выборок, используя анализ объяснения, и я не могу понять, почему 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)

Я создал пользовательскийindex:

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 ]

6 голосов
/ 04 июня 2011

Если вы хотите, чтобы индекс 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

Если вы не хотите создавать дополнительный индекс, а столбец - это ТЕКСТ, вы можете заменить "compte LIKE '6%'" на "compte> = '6' И compte <'7'", который являетсяпростое условие диапазона индекса. </p>

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

В вашем случае это решение, вероятно, лучше.

2 голосов
/ 03 июня 2011

Похоже, что СУБД оценивает, что JOIN для утверждений будет гораздо более ограничительным, чем фильтрация вычислений, а затем объединение.

Варианты могут быть ...
1. Поместить индекс в assentaments.compte_id
2. Измените свой индекс на comptes, чтобы он включал id в качестве первого проиндексированного поля.


Первый параметр может разрешить изменение плана выполнения: отфильтровать результаты, затем присоединиться к утверждениям.

Второй параметр может позволить плану выполнения остатьсято же самое, но разрешить использование индекса.

0 голосов
/ 03 июня 2011

Есть несколько вещей, которые вы могли бы / должны сделать.Во-первых:

SELECT SUM(a.deure)-SUM(a.haver) as Value

SUM() будет касаться каждой строки, которая соответствует ... никоим образом не INDEX этой операции.

FROM assentaments a, comptes c

При отладке запросов мне легчеиспользуйте натуральный JOIN вместо явного JOIN.Планировщик запросов высвобождается немного чаще, и часто выбор становится лучшим.Это не тот случай, просто общий комментарий, однако.Вот где есть вероятные несоответствия между вашими INDEX и вашим запросом.

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 от наивысшей мощности до наименьшего.

Я подозреваю, что три INDEX будут быстрее выполнять растровое или хеш-соединение.Суть проблемы в том, что 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 также может быть полезнымпотому что PostgreSQL, скорее всего, будет делать index_scan на указанную дату в зависимости от количества строк в таблице assentaments.

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.

0 голосов
/ 03 июня 2011

Я бы попробовал с

  • составной индекс (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%'

, что означает, что индекс не может быть использован.Если это так, вы можете преобразовать поле в тип char.

0 голосов
/ 03 июня 2011

Это чаще всего происходит из-за неверной статистики по индексу, то есть если индекс недостаточно избирателен (например, много повторяющихся значений), доступ и фильтрация по индексу могут быть даже более длительными, чем выполнение seq scan.

Достаточно ли избирательны ваши значения по c.codi_compte?Может быть, у вас слишком много нулевых значений?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...