PostgreSQL 9.6データベースにテーブルstation_logs
があります。
Column | Type |
---------------+-----------------------------+
id | bigint | bigserial
station_id | integer | not null
submitted_at | timestamp without time zone |
level_sensor | double precision |
Indexes:
"station_logs_pkey" PRIMARY KEY, btree (id)
"uniq_sid_sat" UNIQUE CONSTRAINT, btree (station_id, submitted_at)
各level_sensor
について、submitted_at
に基づいて最後のstation_id
値を取得しようとしています。一意のstation_id
値は約400個あり、station_id
ごとに1日あたり約2万行あります。
インデックスを作成する前に:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
一意(コスト= 4347852.14..4450301.72行= 89幅= 20)(実際の時間= 22202.080..27619.167行= 98ループ= 1) ->ソート(コスト= 4347852.14..4399076.93行= 20489916幅= 20)(実際の時間= 22202.077..26540.827 rows = 20489812 loops = 1) ソートキー:station_id、submitted_at DESC ソート方法:外部マージディスク:681040kB -> Seq station_logsでスキャン(コスト= 0.00..598895.16行= 20489916幅= 20)(実際の時間= 0.023..3443.587行= 20489812ループ= $ 計画時間:0.072 ms 実行時間:27690.644 MS
インデックスを作成しています:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
インデックスを作成した後、同じクエリに対して:
一意(コスト= 0.56..2156367.51行= 89幅= 20)(実際の時間= 0.184..16263.413行= 98ループ= 1) -> station_logsのstation_id__submitted_atを使用したインデックススキャン(コスト= 0.56..2105142.98 rows = 20489812 width = 20)(実際の時間= 0.181..1 $ 計画時間:0.206 ms 実行時間:16263.490 ms
このクエリをより速くする方法はありますか?たとえば1秒のように、16秒はまだ多すぎます。
ステーション数が400の場合のみ、このクエリは大幅に高速になります。
SELECT s.station_id, l.submitted_at, l.level_sensor
FROM station s
CROSS JOIN LATERAL (
SELECT submitted_at, level_sensor
FROM station_logs
WHERE station_id = s.station_id
ORDER BY submitted_at DESC NULLS LAST
LIMIT 1
) l;
dbfiddle ここ
(このクエリの計画、Abelistoの代替案と元の計画の比較)
OPによって提供される結果EXPLAIN ANALYZE
:
ネストされたループ(コスト= 0.56..356.65行= 102幅= 20)(実際の時間= 0.034..0.979行= 98ループ= 1) ->ステーションsのシーケンススキャン(コスト= 0.00..3.02 rows = 102 width = 4)(実際の時間= 0.009..0.016 rows = 102 loops = 1) ->制限(cost = 0.56..3.45 rows = 1 width = 16)(実際の時間= 0.009。 .0.009行= 1ループ= 102) -> Station_logsのstation_id__submitted_atを使用したインデックススキャン(コスト= 0.56..664062.38行= 230223幅= 16)(実際の時間= 0.009 $ インデックス条件: (station_id = s.id) 計画時間:0.542 ms 実行時間: 1.013ミリ秒 -!!
必要な唯一のindexは、作成したstation_id__submitted_at
です。 UNIQUE
制約uniq_sid_sat
も基本的に機能します。両方を維持することは、ディスク領域と書き込みパフォーマンスの無駄のようです。
NULLS LAST
が定義されていないORDER BY
であるため、 submitted_at
をNOT NULL
に追加しました。可能であれば、理想的には、NOT NULL
制約を列submitted_at
に追加し、追加のインデックスを削除して、クエリからNULLS LAST
を削除します。
submitted_at
をNULL
にできる場合は、このUNIQUE
インデックスを作成して、現在のインデックスと一意制約の両方を置き換えます。
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
考慮してください:
これは、関連するstation_id
(通常はPK)ごとに1行の個別のテーブルstation
を想定しています。どちらにしても。ない場合は作成してください。繰り返しますが、veryこのrCTE手法では高速です。
CREATE TABLE station AS
WITH RECURSIVE cte AS (
(
SELECT station_id
FROM station_logs
ORDER BY station_id
LIMIT 1
)
UNION ALL
SELECT l.station_id
FROM cte c
, LATERAL (
SELECT station_id
FROM station_logs
WHERE station_id > c.station_id
ORDER BY station_id
LIMIT 1
) l
)
TABLE cte;
フィドルでも使っています。同様のクエリを使用して、station
テーブルなしでタスクを直接解決できます-作成する確信がない場合。
詳細な手順、説明、代替案:
クエリは非常に高速になるはずです。それでも読み取りパフォーマンスを最適化する必要がある場合のみ...
joanoloのように、インデックスの最後の列としてlevel_sensor
を追加してインデックスのみのスキャンを許可することは意味があるかもしれませんコメント付き 。
Con:インデックスを大きくします-これを使用するすべてのクエリに少しコストが追加されます。
Pro:実際にインデックススキャンのみを取得する場合、手元のクエリはヒープページにまったくアクセスする必要がないため、約2倍になります。速い。しかし、これは非常に高速なクエリにとっては実質的な利益ではないかもしれません。
ただし、あなたのケースでうまくいくとは思いません。あなたは言及しました:
...
station_id
ごとに1日あたり約2万行。
通常、これは書き込み負荷が絶え間ないことを示します(station_id
ごとに1つ、5秒ごとに1つ)。そして、latest行に興味があります。インデックスのみのスキャンは、すべてのトランザクションから見えるヒープページに対してのみ機能します(可視性マップのビットが設定されています)。書き込み負荷に対応するには、テーブルに対して非常に積極的なVACUUM
設定を実行する必要がありますが、それでもほとんどの場合機能しません。私の仮定が正しい場合、インデックスのみのスキャンは実行されません。しないでくださいインデックスにlevel_sensor
を追加してください。
OTOH、私の仮定が成り立ち、あなたのテーブルが非常に大きくなっている場合、BRINインデックス役立つかもしれません。関連:
または、さらに特殊化された、より効率的な:関連性のない行の大部分を切り取るための最新の追加のみの部分インデックス:
CREATE INDEX station_id__submitted_at_recent_idx ON station_logs(station_id, submitted_at DESC NULLS LAST)
WHERE submitted_at > '2017-06-24 00:00';
新しい行が存在する必要があることを知っているタイムスタンプを選択します。次のように、一致するWHERE
条件をすべてのクエリに追加する必要があります。
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
インデックスとクエリを適宜調整する必要があります。
詳細と関連する回答:
古典的な方法を試してください:
create index idx_station_logs__station_id on station_logs(station_id);
create index idx_station_logs__submitted_at on station_logs(submitted_at);
analyse station_logs;
with t as (
select station_id, max(submitted_at) submitted_at
from station_logs
group by station_id)
select *
from t join station_logs l on (
l.station_id = t.station_id and l.submitted_at = t.submitted_at);
ThreadStarterによるEXPLAIN ANALYZE
Nested Loop (cost=701344.63..702110.58 rows=4 width=155) (actual time=6253.062..6253.544 rows=98 loops=1)
CTE t
-> HashAggregate (cost=701343.18..701344.07 rows=89 width=12) (actual time=6253.042..6253.069 rows=98 loops=1)
Group Key: station_logs.station_id
-> Seq Scan on station_logs (cost=0.00..598894.12 rows=20489812 width=12) (actual time=0.034..1841.848 rows=20489812 loop$
-> CTE Scan on t (cost=0.00..1.78 rows=89 width=12) (actual time=6253.047..6253.085 rows=98 loops=1)
-> Index Scan using station_id__submitted_at on station_logs l (cost=0.56..8.58 rows=1 width=143) (actual time=0.004..0.004 rows=$
Index Cond: ((station_id = t.station_id) AND (submitted_at = t.submitted_at))
Planning time: 0.542 ms
Execution time: 6253.701 ms