Медленно выберите отдельный запрос на postgres - PullRequest
9 голосов
/ 17 мая 2011

Я довольно часто выполняю следующие два запроса к таблице, которая, по сути, собирает информацию журналирования. Оба выбирают разные значения из огромного количества строк, но с менее чем 10 различными значениями.

Я проанализировал оба «разных» запроса, выполненных страницей:

marchena=> explain select distinct auditrecor0_.bundle_id as col_0_0_ from audit_records auditrecor0_;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 HashAggregate  (cost=1070734.05..1070734.11 rows=6 width=21)
   ->  Seq Scan on audit_records auditrecor0_  (cost=0.00..1023050.24 rows=19073524 width=21)
(2 rows)

marchena=> explain select distinct auditrecor0_.server_name as col_0_0_ from audit_records auditrecor0_;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 HashAggregate  (cost=1070735.34..1070735.39 rows=5 width=13)
   ->  Seq Scan on audit_records auditrecor0_  (cost=0.00..1023051.47 rows=19073547 width=13)
(2 rows)

Оба выполняют последовательное сканирование столбцов. Однако, если я отключаю enable_seqscan (несмотря на имя, это только запрещает выполнение сканирования последовательности для столбцов с индексами), запрос использует индекс, но еще медленнее:

marchena=> set enable_seqscan = off;
SET
marchena=> explain select distinct auditrecor0_.bundle_id as col_0_0_ from audit_records auditrecor0_;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=0.00..19613740.62 rows=6 width=21)
   ->  Index Scan using audit_bundle_idx on audit_records auditrecor0_  (cost=0.00..19566056.69 rows=19073570 width=21)
(2 rows)

marchena=> explain select distinct auditrecor0_.server_name as col_0_0_ from audit_records auditrecor0_;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=0.00..45851449.96 rows=5 width=13)
   ->  Index Scan using audit_server_idx on audit_records auditrecor0_  (cost=0.00..45803766.04 rows=19073570 width=13)
(2 rows)

Оба столбца bundle_id и server_name имеют индексы btree. Должен ли я использовать другой тип индекса для быстрого выбора различных значений?

Ответы [ 4 ]

15 голосов
/ 18 мая 2011
BEGIN; 
CREATE TABLE dist ( x INTEGER NOT NULL ); 
INSERT INTO dist SELECT random()*50 FROM generate_series( 1, 5000000 ); 
COMMIT;
CREATE INDEX dist_x ON dist(x);


VACUUM ANALYZE dist;
EXPLAIN ANALYZE SELECT DISTINCT x FROM dist;

HashAggregate  (cost=84624.00..84624.51 rows=51 width=4) (actual time=1840.141..1840.153 rows=51 loops=1)
   ->  Seq Scan on dist  (cost=0.00..72124.00 rows=5000000 width=4) (actual time=0.003..573.819 rows=5000000 loops=1)
 Total runtime: 1848.060 ms

PG не может (пока) использовать индекс для разных (пропуская одинаковые значения), но вы можете сделать это:

CREATE OR REPLACE FUNCTION distinct_skip_foo()
RETURNS SETOF INTEGER
LANGUAGE plpgsql STABLE 
AS $$
DECLARE
    _x  INTEGER;
BEGIN
    _x := min(x) FROM dist;
    WHILE _x IS NOT NULL LOOP
        RETURN NEXT _x;
        _x := min(x) FROM dist WHERE x > _x;
    END LOOP;
END;
$$ ;

EXPLAIN ANALYZE SELECT * FROM distinct_skip_foo();
Function Scan on distinct_skip_foo  (cost=0.00..260.00 rows=1000 width=4) (actual time=1.629..1.635 rows=51 loops=1)
 Total runtime: 1.652 ms
7 голосов
/ 17 мая 2011

Вы выбираете разные значения из всей таблицы, что автоматически приводит к последующему сканированию. У вас миллионы строк, поэтому это будет обязательно медленно.

Есть способ ускорить получение различных значений, но он работает только тогда, когда данные имеют известный (и достаточно малый) набор возможных значений. Например, я полагаю, что ваш bundle_id ссылается на какую-то таблицу пакетов, которая меньше. Это означает, что вы можете написать:

select bundles.bundle_id
from bundles
where exists (
      select 1 from audit_records
      where audit_records.bundle_id = bundles.bundle_id
      );

Это должно привести к сканированию вложенного цикла / последовательности на пакетах -> сканирование индекса для Audit_records с использованием индекса на bundle_id.

4 голосов
/ 18 сентября 2012

У меня та же проблема с таблицами> 300 миллионов записей и индексированным полем с несколькими различными значениями.Я не смог избавиться от сканирования seq, поэтому я сделал эту функцию для имитации отдельного поиска с использованием индекса, если он существует.Если ваша таблица имеет несколько различных значений, пропорциональных общему количеству записей, эта функция не годится.Он также должен быть скорректирован для разных значений столбцов. Предупреждение : эта функция широко открыта для инъекций sql и должна использоваться только в защищенной среде.

Объяснить результаты анализа:Запрос с обычным SELECT DISTINCT: общее время выполнения: 598310,705 мсЗапрос с SELECT small_distinct (...): общее время выполнения: 1,156 мс

CREATE OR REPLACE FUNCTION small_distinct(
   tableName varchar, fieldName varchar, sample anyelement = ''::varchar)
   -- Search a few distinct values in a possibly huge table
   -- Parameters: tableName or query expression, fieldName,
   --             sample: any value to specify result type (defaut is varchar)
   -- Author: T.Husson, 2012-09-17, distribute/use freely
   RETURNS TABLE ( result anyelement ) AS
$BODY$
BEGIN
   EXECUTE 'SELECT '||fieldName||' FROM '||tableName||' ORDER BY '||fieldName
      ||' LIMIT 1'  INTO result;
   WHILE result IS NOT NULL LOOP
      RETURN NEXT;
      EXECUTE 'SELECT '||fieldName||' FROM '||tableName
         ||' WHERE '||fieldName||' > $1 ORDER BY ' || fieldName || ' LIMIT 1'
         INTO result USING result;
   END LOOP;
END;
$BODY$ LANGUAGE plpgsql VOLATILE;

Примеры вызовов:

SELECT small_distinct('observations','id_source',1);
SELECT small_distinct('(select * from obs where id_obs > 12345) as temp',
   'date_valid','2000-01-01'::timestamp);
SELECT small_distinct('addresses','state');
1 голос
/ 24 января 2014

На PostgreSQL 9.3, начиная с ответа Дениса:

    select bundles.bundle_id
    from bundles
    where exists (
      select 1 from audit_records
      where audit_records.bundle_id = bundles.bundle_id
      );

, просто добавив «подстановку 1» в подзапрос, я получил ускорение в 60 раз (для моего случая использования с 8 миллионами записей), составной индекс и 10 тыс. комбинаций), от 1800 мс до 30 мс:

    select bundles.bundle_id
    from bundles
    where exists (
      select 1 from audit_records
      where audit_records.bundle_id = bundles.bundle_id limit 1
      );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...