PostgreSQL 8.4を使用して、2つのテーブルのインデックス付き列でorder byを使用して、100万レコードの2つのテーブルを調べようとしています。カラムには5分かかります)。例えば。:
select r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
from requests r
inner join samples s on (s.code = r.sample_code)
order by s.barcode asc , r.code asc
limit 21;
テーブル情報:
CREATE TABLE public.samples (
code BIGINT NOT NULL,
barcode VARCHAR(40) NOT NULL,
registry_date TIMESTAMP WITH TIME ZONE NOT NULL,
CONSTRAINT samples_pkey PRIMARY KEY(code)
);
CREATE INDEX idx_samp_barcode ON public.samples (barcode);
CREATE INDEX idx_samp_barcode_code ON public.samples (barcode, code);
CREATE INDEX idx_samp_barcode_code_desc ON public.samples (barcode DESC, code DESC);
CREATE INDEX idx_test_identifier_desc ON public.samples (barcode DESC);
CREATE TABLE public.requests (
code BIGINT NOT NULL,
test_code INTEGER NOT NULL,
sample_code BIGINT NOT NULL,
CONSTRAINT requests_pkey PRIMARY KEY(code),
CONSTRAINT "Requests_S_fk" FOREIGN KEY (sample_code)
REFERENCES public.samples(code)
ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE,
CONSTRAINT "Requests_T_fk" FOREIGN KEY (test_code)
REFERENCES public.tests(code)
ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE
);
CREATE INDEX idx_req_test_code ON public.requests (test_code);
CREATE INDEX idx_req_test_code_desc ON public.requests (test_code DESC);
CREATE INDEX request_sample_code_index ON public.requests (sample_code);
CREATE INDEX requests_sample_code_desc_idx ON public.requests (sample_code DESC, code DESC);
CREATE INDEX requests_sample_code_idx ON public.requests (sample_code, code);
s.code
for smaes.barcode
。パフォーマンスを向上させるにはどうすればよいですか?
これは一目でわかるよりも複雑な問題です。他の2つの列を結合しながら、それぞれが異なるテーブルの2つの列で並べ替えています。これはPostgresが提供されたインデックスを使用することを不可能にし、そして(非常に)高価な順次スキャンをデフォルトにしなければなりません。以下は、dba.SEの関連ケースです。
最高のパフォーマンスを得るには、2つのインデックスが必要です。どちらもすでに存在しています。
CREATE INDEX idx_samp_barcode_code ON public.samples (barcode, code);
CREATE INDEX requests_sample_code_idx ON public.requests (sample_code, code);
しかし、あなたが持っているプレーンなクエリはそれらを利用することはできません。
Postgres 8.4はかなりのハードルです。しかし、私は再帰的なCTEの方法を見つけたと思います。
WITH RECURSIVE cte AS (
( -- all parentheses are required
SELECT r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
FROM (
SELECT s.barcode
FROM samples s
-- WHERE EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)
ORDER BY s.barcode
LIMIT 1 -- get smallest barcode to start with
) s0
JOIN samples s USING (barcode) -- join all samples with same barcode
JOIN requests r ON r.sample_code = s.code
ORDER BY r.code -- start with ordered list
)
UNION ALL
(
SELECT r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
FROM (
SELECT s.barcode
FROM cte c
JOIN samples s ON s.barcode > c.barcode
-- WHERE EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)
ORDER BY s.barcode
LIMIT 1 -- get next higher barcode
) s0
JOIN samples s USING (barcode) -- join all samples with same barcode
JOIN requests r ON r.sample_code = s.code
ORDER BY r.code -- again, ordered list
)
)
TABLE cte
LIMIT 21;
テスト済みで、9.4ページで動作します。インデックスを使用します(一部はpg 9.2+のインデックスのみのスキャンで)。
pg 8.4にはすでに再帰的なCTEがあります。残りは基本的なSQLです(TABLE
もpg 8.4で使用できます(そしてSELECT * FROM
で置き換えることができます)。パーティーを台無しにする制限がないことを願っています。インストールされていません。 pg 8.4以降。
コメントされた部分:
-- WHERE EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)
コメントで除外 のように、リクエストがまったくないサンプルがある場合に必要になります。
クエリはこれに依存します 文書化された再帰CTEの動作 :
ヒント:再帰クエリ評価アルゴリズムは、出力をbreadth-first検索順序で生成します。
大胆な強調鉱山。そして これも :
これは、PostgreSQLの実装が
WITH
クエリの行を親クエリによって実際にフェッチされるのと同じ数だけ評価するため、機能します。他のシステムでは動作が異なる可能性があるため、このトリックを本番環境で使用することはお勧めしません。また、外部クエリで再帰クエリの結果を並べ替えたり、他のテーブルに結合したりしても、通常は機能しません。
大胆な強調鉱山。したがって、PostgreSQLでの動作が保証されるのは、外部クエリに別のORDER BY
を追加しない場合のみです。
これは、外部クエリの(比較的)小さいLIMIT
の場合は高速です。当面のケースでは、非常に高速である必要があります。クエリは大規模なLIMIT
には適していませんになります。
私が考えることができるもう1つのオプションは、plpgsqlを使用した手続き型ソリューションです。特に繰り返し呼び出しの場合は、まだ少し速いはずです:
CREATE OR REPLACE FUNCTION f_demo(_limit int)
RETURNS TABLE (code int8, test_code int, sample_code int8
, barcode varchar, registry_date timestamptz) AS
$func$
DECLARE
_barcode text;
_n int;
_rest int := _limit; -- init with _limit parameter
BEGIN
FOR _barcode IN
SELECT DISTINCT s.barcode
FROM samples s
-- WHERE EXISTS (SELECT 1 FROM requests WHERE r.sample_code = s.code)
ORDER BY s.barcode
LOOP
RETURN QUERY
SELECT r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
FROM samples s
JOIN requests r ON r.sample_code = s.code
WHERE s.barcode = _barcode
ORDER BY r.code
LIMIT _limit;
GET DIAGNOSTICS _n = ROW_COUNT;
_rest := _rest - _n;
EXIT WHEN _rest < 1;
END LOOP;
END
$func$ LANGUAGE plpgsql;
コール:
SELECT * FROM f_demo(21);
MATERIALIZED VIEW
大きなOFFSET
は、大きなLIMIT
と同様の効果があります。スキップされたすべての行はI/Oに追加されませんが、最初の行afterオフセットを決定するには、それらを計算する必要があります。それが必要な場合は、行番号とそのインデックスを追加した MATERIALIZED VIEW
を使用します。
Postgres 9.3以降には組み込みの機能があり、古いソフトウェアを使用して手動でソリューションを作成する必要があります。ただし、それほど複雑ではありません。基本的に、事前定義されたSELECT
ステートメントまたはVIEW
などから入力されたスナップショットテーブル。基本的に、次のようにテーブルを作成します。
CREATE TABLE req_sample AS
SELECT row_number() OVER (ORDER BY s.barcode, r.code) AS rn
, r.code, r.test_code, r.sample_code, s.barcode, s.registry_date
FROM requests r
JOIN samples s on (s.code = r.sample_code)
ORDER by s.barcode, r.code;
ALTER TABLE req_sample ADD CONSTRAINT req_sample_rk PRIMARY KEY (code);
CREATE INDEX foo ON req_sample (rn);
そのSELECTを関数、ビュー、またはプレーンクエリテキストとして保存します。 1つのトランザクションでの更新(高価):
BEGIN;
-- drop PK & index
TRUNCATE req_sample;
INSERT INTO req_sample SELECT ... ;
-- add PK & index
COMMIT;
barcode
列基になるテーブルが大幅に変更され、「最新の」結果が必要な場合、MVはより高価になります。もう1つ中程度の安価なオプション:barcode
をrequests
テーブルに保存します冗長。これをsamples
("Requests_S_fk"
)へのFK制約の2番目の列として含め、そのデータをON UPDATE CASCADE
にすることで、古いデータや競合するデータを回避できます。次に、requests
の(barcode, code)
のインデックスを操作できます。
最後の2つのオプションについては、前のページの値のWHERE
句をOFFSET
に置き換えて、直接適用可能なインデックスを使用してページ付けを簡略化できます。 use-the-index-luke.comの "Pagination done the PostgreSQL way"に関するこのプレゼンテーションを検討してください 。