#postgresql #window-functions #postgresql-performance
#postgresql #окно-функции #postgresql-производительность
Вопрос:
В некоторых случаях PostgreSQL не отфильтровывает разделы оконной функции, пока они не будут вычислены, в то время как в очень похожем сценарии PostgreSQL фильтрует строку перед выполнением вычисления оконной функции.
Таблицы, используемые для минимального STR — log
это основная таблица данных, каждая строка содержит increment
absolute
значение или. Абсолютное значение сбрасывает текущее counter
значение с новым базовым значением. Оконные функции должны обрабатывать все журналы для данного account_id
, чтобы вычислить правильный текущий итог. View использует подзапрос, чтобы гарантировать, что базовые log
строки не фильтруются ts
, в противном случае это нарушило бы работу оконной функции.
CREATE TABLE account(
id serial,
name VARCHAR(100)
);
CREATE TABLE log(
id serial,
absolute int,
incremental int,
account_id int,
ts timestamp,
PRIMARY KEY(id),
CONSTRAINT fk_account
FOREIGN KEY(account_id)
REFERENCES account(id)
);
CREATE FUNCTION get_running_total_func(
aggregated_total int,
absolute int,
incremental int
) RETURNS int
LANGUAGE sql IMMUTABLE CALLED ON NULL INPUT AS
$
SELECT
CASE
WHEN absolute IS NOT NULL THEN absolute
ELSE COALESCE(aggregated_total, 0) incremental
END
$;
CREATE AGGREGATE get_running_total(integer, integer) (
sfunc = get_running_total_func,
stype = integer
);
Медленный просмотр:
CREATE VIEW test_view
(
log_id,
running_value,
account_id,
ts
)
AS
SELECT log_running.* FROM
(SELECT
log.id,
get_running_total(
log.absolute,
log.incremental
)
OVER(
PARTITION BY log.account_id
ORDER BY log.ts RANGE UNBOUNDED PRECEDING
),
account.id,
ts
FROM log log JOIN account account ON log.account_id=account.id
) AS log_running;
CREATE VIEW
postgres=# EXPLAIN ANALYZE SELECT * FROM test_view WHERE account_id=1;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Subquery Scan on log_running (cost=12734.02..15981.48 rows=1 width=20) (actual time=7510.851..16122.404 rows=20 loops=1)
Filter: (log_running.id_1 = 1)
Rows Removed by Filter: 99902
-> WindowAgg (cost=12734.02..14732.46 rows=99922 width=32) (actual time=7510.830..14438.783 rows=99922 loops=1)
-> Sort (cost=12734.02..12983.82 rows=99922 width=28) (actual time=7510.628..9312.399 rows=99922 loops=1)
Sort Key: log.account_id, log.ts
Sort Method: external merge Disk: 3328kB
-> Hash Join (cost=143.50..2042.24 rows=99922 width=28) (actual time=169.941..5431.650 rows=99922 loops=1)
Hash Cond: (log.account_id = account.id)
-> Seq Scan on log (cost=0.00..1636.22 rows=99922 width=24) (actual time=0.063..1697.802 rows=99922 loops=1)
-> Hash (cost=81.00..81.00 rows=5000 width=4) (actual time=169.837..169.865 rows=5000 loops=1)
Buckets: 8192 Batches: 1 Memory Usage: 240kB
-> Seq Scan on account (cost=0.00..81.00 rows=5000 width=4) (actual time=0.017..84.639 rows=5000 loops=1)
Planning Time: 0.199 ms
Execution Time: 16127.275 ms
(15 rows)
Быстрое изменение только account.id
для просмотра -> log.account_id
(!):
CREATE VIEW test_view
(
log_id,
running_value,
account_id,
ts
)
AS
SELECT log_running.* FROM
(SELECT
log.id,
get_running_total(
log.absolute,
log.incremental
)
OVER(
PARTITION BY log.account_id
ORDER BY log.ts RANGE UNBOUNDED PRECEDING
),
log.account_id,
ts
FROM log log JOIN account account ON log.account_id=account.id
) AS log_running;
CREATE VIEW
postgres=# EXPLAIN ANALYZE SELECT * FROM test_view WHERE account_id=1;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------
Subquery Scan on log_running (cost=1894.96..1895.56 rows=20 width=20) (actual time=34.718..45.958 rows=20 loops=1)
-> WindowAgg (cost=1894.96..1895.36 rows=20 width=28) (actual time=34.691..45.307 rows=20 loops=1)
-> Sort (cost=1894.96..1895.01 rows=20 width=24) (actual time=34.367..35.925 rows=20 loops=1)
Sort Key: log.ts
Sort Method: quicksort Memory: 26kB
-> Nested Loop (cost=0.28..1894.53 rows=20 width=24) (actual time=0.542..34.066 rows=20 loops=1)
-> Index Only Scan using account_pkey on account (cost=0.28..8.30 rows=1 width=4) (actual time=0.025..0.054 rows=1 loops=1)
Index Cond: (id = 1)
Heap Fetches: 1
-> Seq Scan on log (cost=0.00..1886.03 rows=20 width=24) (actual time=0.195..32.937 rows=20 loops=1)
Filter: (account_id = 1)
Rows Removed by Filter: 99902
Planning Time: 0.297 ms
Execution Time: 47.300 ms
(14 rows)
Это ошибка в реализации PostgreSQL? Похоже, что это изменение в определении представления вообще не должно влиять на производительность, PostgreSQL должен иметь возможность фильтровать данные перед применением функции окна для всего набора данных.
Комментарии:
1. Сначала я бы создал индекс для log.account_id и log.ts, прежде чем что-либо еще. Видите ли вы какую-либо разницу в плане запроса, когда эти индексы доступны?
2. Я добавил индексные и повторные запросы, индекс ничего не меняет. Насколько я понимаю, проблема не в последовательном сканировании
log
, а в отсутствииfilter
beforeWindowAgg
.3. Строки, удаленные фильтром: 99902 все еще происходит, даже с индексом в account_id ?
4. Вы уверены в этой части функции: COALESCE(total, 0) НЕ РАВНО NULL Из-за COALESCE , это всегда будет ВЕРНО, независимо от значения total .
5. Вывод с индексом идентичен выводу «Медленного просмотра», только цифры немного меняются, без каких-либо серьезных изменений. Я исправил эту часть
COALESCE
, спасибо! Однако это не меняет тайминги.