価格情報が格納されているテーブルがあり、PostgreSQL9.5データベースに約1,300万行が格納されています。
_CREATE TABLE public.de_tt_priceinfo (
id integer NOT NULL DEFAULT nextval('priceinfo_id_seq'::regclass),
station_id character varying(60),
recieved timestamp with time zone NOT NULL DEFAULT now(),
e5 numeric(4,3),
e10 numeric(4,3),
diesel numeric(4,3),
CONSTRAINT de_tt_priceinfo_id_pkey PRIMARY KEY (id)
);
CREATE INDEX de_tt_priceinfo_recieved_station_id_idx
ON public.de_tt_priceinfo (recieved, station_id COLLATE pg_catalog."default");
CREATE INDEX index_station_id
ON public.de_tt_priceinfo (station_id COLLATE pg_catalog."default");
_
このテーブルから、このテーブルをクエリする3,200万人の通勤者をシミュレートする必要があるため、特定の時点での最新の価格を最大のパフォーマンスで抽出する必要があります(一度にではなく、それでも)。
動作するクエリがあります!
_SELECT station_id, e5, e10, diesel, recieved FROM de_tt_priceinfo a
WHERE a.recieved = (SELECT MAX(recieved) FROM de_tt_priceinfo b
WHERE a.station_id = b.station_id
AND recieved <= '2014-09-25 08:45:12'::TIMESTAMPTZ)
AND station_id IN('0C91A93A-a-b-c-d', '578C44BB-a-b-c-d', '6F2F48A8-a-b-c-d'
, '9982BE74-a-b-c-d', 'A24C612B-a-b-c-d', 'BEC3EF55-a-b-c-d'
, 'F5137488-a-b-c-d')
_
このクエリのパフォーマンスは使用できません。実行時間は約900ms変動します。結果は次のようになります
_0C91A93A-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 08:17:50.000000"
578C44BB-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 08:00:09.000000"
6F2F48A8-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 07:08:57.000000"
9982BE74-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 08:29:55.000000"
A24C612B-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 08:00:09.000000"
BEC3EF55-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 06:53:49.000000"
F5137488-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 07:44:55.000000"
_
そこで、少し調べてみると、再帰的CTE、ルーズインデックススキャン、DBAに関するいくつかの回答など、非常に近いと思われる流行語が見つかりましたが、ニーズに合わせて変更することはできませんでした。
私が正しく理解していれば、再帰CTEは、必要なデータをクエリする最も簡単な方法です。
私がこれまでに得たものはこれです:
_WITH RECURSIVE cte AS (
(
SELECT station_id, e5, e10, diesel, recieved
FROM de_tt_priceinfo
WHERE recieved <= '2014-09-25 08:45:00'::TIMESTAMPTZ
AND station_id IN('0C91A93A-a-b-c-d', '578C44BB-a-b-c-d', '6F2F48A8-a-b-c-d'
, '9982BE74-a-b-c-d', 'A24C612B-a-b-c-d', 'BEC3EF55-a-b-c-d'
, 'F5137488-a-b-c-d')
ORDER BY station_id, recieved DESC NULLS LAST
LIMIT 1
)
UNION ALL
(
SELECT u.station_id, u.e5, u.e10, u.diesel, u.recieved
FROM cte c
JOIN de_tt_priceinfo u ON u.recieved > c.recieved
WHERE u.recieved <= '2014-09-25 08:45:00'::TIMESTAMPTZ -- repeat condition!
AND u.station_id IN('0C91A93A-a-b-c-d', '578C44BB-a-b-c-d', '6F2F48A8-a-b-c-d'
, '9982BE74-a-b-c-d', 'A24C612B-a-b-c-d', 'BEC3EF55-a-b-c-d'
, 'F5137488-a-b-c-d')
ORDER BY u.station_id, u.recieved DESC NULLS LAST LIMIT 1
)
)
SELECT * FROM cte;
_
しかし、これは次の2行を返すだけです。
_0C91A93A-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 08:17:50.000000"
9982BE74-a-b-c-d, 1.xxx, 1.xxx, 1.xxx, "2014-09-25 08:29:55.000000"
_
更新:
SELECT Version();
PostgreSQL 9.5devel on x86_64-pc-linux-gnu、コンパイル済みx86_64-pc-linux-gnu-gcc(Gentoo 4.8.3 p1.1、pie-0.5.9)4.8.3、64 -ビット#接続 listen_addresses = '*' max_connections = 16 #ロギング log_destination = 'csvlog' log_directory = 'pg_log' logging_collector = on log_filename = 'postgres-%Y-%m-%d_%H%M%S.log' log_rotation_age = 1d log_rotation_size = 1GB log_min_duration_statement = 500ms #log_checkpoints = on #log_connections = on #log_disconnections = on log_lock_wait on #log_temp_files = 0 #メモリ shared_buffers = 1GB temp_buffers = 32MB work_mem = 256MB maintenance_work_mem = 1GB effective_cache_size = 8GB #チェックポイント(ディスクに書き込むタイミング) wal_buffers = 16MB checkpoint_completion_target = 0.9 checkpoint_timeout = 30min checkpoint_segments = 32 random_page_cost = 1.1 #インポートのみ! #autovacuum = off fsync = off synchronous_commit = of f full_page_writes = off
注:交換しました どこでもrecieved
received
で。
何よりもまず、クエリのタイプにとって、これははるかに優れたインデックスです。
CREATE INDEX de_tt_priceinfo_received_station_id_idx
ON public.de_tt_priceinfo (station_id, received); -- note the reversed order
組み合わせは一意であると想定されているため(私は推測します)、代わりに(station_id, receved)
にUNIQUE
制約を提案します。
ALTER TABLE de_tt_priceinfo ADD CONSTRAINT de_tt_priceinfo_station_id_received
UNIQUE (station_id, received);
インデックスindex_station_id
はほとんど置き換えられており、おそらく今は削除できます。
インデックスde_tt_priceinfo_received_station_id_idx
はまだ使用されている可能性があります。
このすべての背後にあるロジックを必ず理解してください。
基本的なDISTINCT ON
クエリも検討します。
SELECT DISTINCT ON (station_id)
station_id, e5, e10, diesel, received
FROM de_tt_priceinfo
WHERE received <= '2014-09-25 08:45:12'::TIMESTAMPTZ
AND station_id = ANY ('{0C91A93A-a-b-c-d, 578C44BB-a-b-c-d, 6F2F48A8-a-b-c-d
, 9982BE74-a-b-c-d, A24C612B-a-b-c-d, BEC3EF55-a-b-c-d
, F5137488-a-b-c-d}'::varchar[])
ORDER BY station_id, received DESC;
しかし、ステーションごとにたくさんの行があるように見えるので、それは輝いていません。代わりに:
SELECT *
FROM (
VALUES
('0C91A93A-a-b-c-d'::varchar)
, ('578C44BB-a-b-c-d')
, ('6F2F48A8-a-b-c-d')
, ('9982BE74-a-b-c-d')
, ('A24C612B-a-b-c-d')
, ('BEC3EF55-a-b-c-d')
, ('F5137488-a-b-c-d')
) s(station_id)
LEFT JOIN LATERAL (
SELECT e5, e10, diesel, received
FROM de_tt_priceinfo
WHERE station_id = s.station_id
AND received <= '2014-09-25 08:45:12'::TIMESTAMPTZ
ORDER BY received DESC
LIMIT 1
) p ON TRUE
これは、上記のUNIQUE
制約(または同等のインデックス)と組み合わせてダイナマイトにする必要があります。
詳細な説明:
数百万行のテーブルの場合、簡単に可能な限りストレージを最適化するのにお金がかかります。すべてをより小さく、より速くします。
それが私がそれを設計する方法です:
CREATE TABLE station (
station_id serial PRIMARY KEY
, station text
, CHECK (length(station) < 61) -- ?? optional, you decide
);
CREATE TABLE priceinfo (
priceinfo_id serial PRIMARY KEY
, station_id integer NOT NULL REFERENCES station ON UPDATE CASCADE
, received timestamptz NOT NULL DEFAULT now(),
, e5 integer -- price in 0.1 Cent
, e10 integer -- price in 0.1 Cent
, diesel integer -- price in 0.1 Cent
, CONSTRAINT priceinfo_station_id_received UNIQUE (station_id, received)
);
CREATE INDEX priceinfo_received_idx ON public.priceinfo (received);
priceinfo
の行サイズは60バイト(24ヒープタプルヘッダー+ nullビットマップ; 32バイトデータ; 4バイトアイテムポインタ)になります。94バイト(24 + 66 + 4)元のテーブル。これは、例のようにassuming16文字の文字列です。すべてが約36%小さく(またはそれ以上?)、かなり速くなります。
(station_id, received)
の重要なインデックスは、32バイトではなく、インデックスタプルあたりのデータの8バイトになります。 /またはさらに多く(!)-それぞれプラスオーバーヘッド。さらに、station_id
のinteger
番号の処理は、通常、その上にCOLLATION
が付いたテキストよりも高速です。
詳細:
クエリは最初にstation
テーブルからstation_id
をフェッチします。これは安価です。
価格は、0.1セントを意味するinteger
の数値として保存されます。 (4バイト10バイトの代わりに元のnumeric(4,3)
0.1を掛けてセントを取得しますまたは0.001で表示用の€を取得します。非常にシンプルで高速です。
UUID
エラーメッセージの文字列はかなり長く見え、実際には通常のように見えます UUID
数値:
871828b4-37e5-419c-b7a5-cdbe1e1c0148
その場合は、uuid
データ型を使用してください。古いままにするという私のデザインを採用するかどうか。少なくともuuid
データ型に切り替えると、あらゆる面で全体的に大きな利益が得られます。