PostgreSQL 11 используется для параллельного сканирования последовательностей на многораздельных таблицах, где должно хватить индекса - PullRequest
0 голосов
/ 25 февраля 2019

Проблема в том, что я продолжаю сканировать seq по довольно простому запросу для очень тривиальной установки.Что я делаю не так?

  • Postgres 11 на Windows Server 2016
  • Изменения конфигурации: constraint_exclusion = partition
  • Одна таблица разбита на 200 подтаблиц, десяткимиллионов записей на раздел.
  • Индекс по рассматриваемому полю (при условии, что одна секционирована также)

Вот оператор create:

CREATE TABLE A (
    K int NOT NULL,
    X bigint NOT NULL,
    Date timestamp NOT NULL,
    fy smallint NOT NULL,
    fz decimal(18, 8) NOT NULL,
    fw decimal(18, 8) NOT NULL,
    fv decimal(18, 8) NULL,
    PRIMARY KEY (K, X)
) PARTITION BY LIST (K);

CREATE TABLE A_1 PARTITION OF A FOR VALUES IN (1);
CREATE TABLE A_2 PARTITION OF A FOR VALUES IN (2);
...
CREATE TABLE A_200 PARTITION OF A FOR VALUES IN (200);
CREATE TABLE A_Default PARTITION OF A DEFAULT;

CREATE INDEX IX_A_Date ON A (Date);

Запрос ввопрос:

SELECT K, MIN(Date), MAX(Date)
FROM A 
GROUP BY K

Это всегда дает сканирование последовательности, которое занимает несколько минут, в то время как совершенно очевидно, что нет необходимости в табличных данных вообще, так как поле Date проиндексировано, и я просто запрашиваю первый и последний листего B-дерево.

Первоначально индекс был на (K, Date), и мне быстро показалось, что Postgres не выполнит ни одного запроса в любом запросе, в котором я предполагал, что он используется. Индекс на (Date) сделалхитрость для других запросов, и кажется, что Postgres автоматически запрашивает индексы секцийОднако этот конкретный простой запрос всегда выполняется для последующего сканирования.

Любые мысли приветствуются!

ОБНОВЛЕНИЕ

План запроса (analyze, buffers) выглядит следующим образом:

Finalize GroupAggregate  (cost=4058360.99..4058412.66 rows=200 width=20) (actual time=148448.183..148448.189 rows=5 loops=1)
  Group Key: a_16.k
  Buffers: shared hit=5970 read=548034 dirtied=4851 written=1446
  ->  Gather Merge  (cost=4058360.99..4058407.66 rows=400 width=20) (actual time=148448.166..148463.953 rows=8 loops=1)
    Workers Planned: 2
    Workers Launched: 2
    Buffers: shared hit=5998 read=1919356 dirtied=4865 written=1454
    ->  Sort  (cost=4057360.97..4057361.47 rows=200 width=20) (actual time=148302.271..148302.285 rows=3 loops=3)
        Sort Key: a_16.k
        Sort Method: quicksort  Memory: 25kB
        Worker 0:  Sort Method: quicksort  Memory: 25kB
        Worker 1:  Sort Method: quicksort  Memory: 25kB
        Buffers: shared hit=5998 read=1919356 dirtied=4865 written=1454
        ->  Partial HashAggregate  (cost=4057351.32..4057353.32 rows=200 width=20) (actual time=148302.199..148302.203 rows=3 loops=3)
            Group Key: a_16.k
            Buffers: shared hit=5984 read=1919356 dirtied=4865 written=1454
            ->  Parallel Append  (cost=0.00..3347409.96 rows=94658849 width=12) (actual time=1.678..116664.051 rows=75662243 loops=3)
                Buffers: shared hit=5984 read=1919356 dirtied=4865 written=1454
                ->  Parallel Seq Scan on a_16  (cost=0.00..1302601.32 rows=42870432 width=12) (actual time=0.320..41625.766 rows=34283419 loops=3)
                    Buffers: shared hit=14 read=873883 dirtied=14 written=8
                ->  Parallel Seq Scan on a_19  (cost=0.00..794121.94 rows=26070794 width=12) (actual time=0.603..54017.937 rows=31276617 loops=2)
                    Buffers: shared read=533414
                ->  Parallel Seq Scan on a_20  (cost=0.00..447025.50 rows=14900850 width=12) (actual time=0.347..52866.404 rows=35762000 loops=1)
                    Buffers: shared hit=5964 read=292053 dirtied=4850 written=1446
                ->  Parallel Seq Scan on a_18  (cost=0.00..198330.23 rows=6450422 width=12) (actual time=4.504..27197.706 rows=15481014 loops=1)
                    Buffers: shared read=133826
                ->  Parallel Seq Scan on a_17  (cost=0.00..129272.31 rows=4308631 width=12) (actual time=3.014..18423.307 rows=10340224 loops=1)
                    Buffers: shared hit=6 read=86180 dirtied=1
                ...
                ->  Parallel Seq Scan on a_197  (cost=0.00..14.18 rows=418 width=12) (actual time=0.000..0.000 rows=0 loops=1)
                ->  Parallel Seq Scan on a_198  (cost=0.00..14.18 rows=418 width=12) (actual time=0.001..0.002 rows=0 loops=1)
                ->  Parallel Seq Scan on a_199  (cost=0.00..14.18 rows=418 width=12) (actual time=0.001..0.001 rows=0 loops=1)
                ->  Parallel Seq Scan on a_default  (cost=0.00..14.18 rows=418 width=12) (actual time=0.001..0.002 rows=0 loops=1)
Planning Time: 16.893 ms
Execution Time: 148466.519 ms

ОБНОВЛЕНИЕ 2 Просто чтобы избежать будущих комментариев, таких как «Вы должны индексировать (K, Date)»:

План запроса с обоими индексами на месте точното же самое, числа анализа одинаковы, и даже попадания / чтения буфера почти одинаковы.

1 Ответ

0 голосов
/ 25 февраля 2019

Совокупный переход в параллельные планы можно включить, установив enable_partitionwise_aggregate в on.

Это, вероятно, несколько ускорит ваш запрос, потому что PostgreSQL не имеетпередавать столько данных между параллельными рабочими.

Но похоже, что PostgreSQL недостаточно умен, чтобы понять, что он может использовать индекс для ускорения min и max для каждого раздела, хотядостаточно умен, чтобы делать это с однораздельной таблицей.

Нет прекрасного способа обойти это;Вы можете обратиться к каждому разделу:

SELECT k, min(min_date), max(max_date)
FROM (
   SELECT 1 AS k, MIN(date) AS min_date, MAX(date) AS max_date FROM a_1
   UNION ALL
   SELECT 2, MIN(date), MAX(date) FROM a_2
   UNION ALL
   ...
   SELECT 200, MIN(date), MAX(date) FROM a_200
   UNION ALL
   SELECT k, MIN(date), MAX(date) FROM a_default
) AS all_a
GROUP BY k;

Фу!Здесь явно есть место для улучшения.

Я копался в коде и нашел причину в src/backend/optimizer/plan/planagg.c:

/*
 * preprocess_minmax_aggregates - preprocess MIN/MAX aggregates
 *
 * Check to see whether the query contains MIN/MAX aggregate functions that
 * might be optimizable via indexscans.  If it does, and all the aggregates
 * are potentially optimizable, then create a MinMaxAggPath and add it to
 * the (UPPERREL_GROUP_AGG, NULL) upperrel.
[...]
 */
void
preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
{
[...]                                                                                
    /*
     * Reject unoptimizable cases.
     *
     * We don't handle GROUP BY or windowing, because our current
     * implementations of grouping require looking at all the rows anyway, and
     * so there's not much point in optimizing MIN/MAX.
     */
    if (parse->groupClause || list_length(parse->groupingSets) > 1 ||
        parse->hasWindowFuncs)
        return;

По сути, PostgreSQL выдает ошибку, когда видит предложение GROUP BY.

...