5

私は現在、Postgres 9.2 で複雑な並べ替えの問題に取り組んでいます。この質問で使用されているソース コード (簡略化) は、http ://sqlfiddle.com/#!12/9857e/11 で見つけることができます。

さまざまなタイプのさまざまな列を含む巨大な (>>20Mio 行) テーブルがあります。

CREATE TABLE data_table
(
  id bigserial PRIMARY KEY,
  column_a character(1),
  column_b integer
  -- ~100 more columns
);

このテーブルを 2 列( ASC ) で並べ替えたいとしましょう。しかし、後で並べ替えられた出力に行を挿入する必要があり、ユーザーはおそらく(並べ替えられた出力の)一度に 100 行しか表示したくないため、単に Order By でそれを行いたくありません。

これらの目標を達成するために、私は次のことを行います。

CREATE TABLE meta_table
(
 id bigserial PRIMARY KEY,
 id_data bigint NOT NULL -- refers to the data_table
);

--Function to get the Column A of the current row
CREATE OR REPLACE FUNCTION get_column_a(bigint)
 RETURNS character AS
 'SELECT column_a FROM data_table WHERE id=$1'
 LANGUAGE sql IMMUTABLE STRICT;

--Function to get the Column B of the current row
CREATE OR REPLACE FUNCTION get_column_b(bigint)
 RETURNS integer AS
 'SELECT column_b FROM data_table WHERE id=$1'
 LANGUAGE sql IMMUTABLE STRICT;

--Creating a index on expression:
CREATE INDEX meta_sort_index
 ON meta_table
 USING btree
 (get_column_a(id_data), get_column_b(id_data), id_data);

そして、data_table の ID を meta_table にコピーします。

INSERT INTO meta_table(id_data) (SELECT id FROM data_table);

後で、同様の単純な挿入を使用してテーブルに行を追加できます。
行 900000 ~ 900099 ( 100 行) を取得するには、次を使用できます。

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
ORDER BY 1,2,3 OFFSET 900000 LIMIT 100;

(すべてのデータが必要な場合は、data_table に追加の INNER JOIN を使用します。)
結果のプランは次のとおりです。

Limit (cost=498956.59..499012.03 rows=100 width=8)
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..554396.21 rows=1000000 width=8)

これは非常に効率的な計画です (Index Only Scans は Postgres 9.2 の新機能です)。しかし、行 20'000'000 - 20'000'099 ( 100 行
) を取得したい場合はどうすればよいでしょうか? 同じプランで、実行時間が大幅に長くなります。さて、オフセット パフォーマンスを向上させるには ( PostgreSQL での OFFSET パフォーマンスの向上)、次のことができます (100,000 行ごとに別のテーブルに保存したと仮定します)。

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
WHERE (get_column_a(id_data), get_column_b(id_data), id_data ) >= (get_column_a(587857), get_column_b(587857), 587857 )
ORDER BY 1,2,3 LIMIT 100;

これははるかに高速に実行されます。結果の計画は次のとおりです。

Limit (cost=0.51..61.13 rows=100 width=8)
-> Index Only Scan using meta_sort_index on meta_table (cost=0.51..193379.65 rows=318954 width=8)
Index Cond: (ROW((get_column_a(id_data)), (get_column_b(id_data)), id_data) >= ROW('Z'::bpchar, 27857, 587857))

これまでのところ、すべてが完璧に機能し、postgres は素晴らしい仕事をしています!

2列目の順序をDESCに変更したいとしましょう。
ただし、> 演算子は両方の列 ASC を比較するため、WHERE 句を変更する必要があります。上記 (ASC Ordering) と同じクエリは、次のように書くこともできます。

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
WHERE 
   (get_column_a(id_data) > get_column_a(587857)) 
OR (get_column_a(id_data) = get_column_a(587857) AND ((get_column_b(id_data) > get_column_b(587857)) 
OR (                                                  (get_column_b(id_data) = get_column_b(587857)) AND (id_data >= 587857)))) 
ORDER BY 1,2,3 LIMIT 100;

プランが変更され、クエリが遅くなります。

Limit (cost=0.00..1095.94 rows=100 width=8)
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..1117877.41 rows=102002 width=8)
Filter: (((get_column_a(id_data)) > 'Z'::bpchar) OR (((get_column_a(id_data)) = 'Z'::bpchar) AND (((get_column_b(id_data)) > 27857) OR (((get_column_b(id_data)) = 27857) AND (id_data >= 587857)))))

DESC-Ordering で効率的な古いプランを使用するにはどうすればよいですか?
問題を解決するためのより良いアイデアはありますか?

(私はすでに独自の演算子クラスで独自の型を宣言しようとしましたが、それは遅すぎます)

4

1 に答える 1

4

アプローチを再考する必要があります。どこから始めますか?これは明確な例であり、基本的に、SQL に対して行っている機能的なアプローチのパフォーマンス面での限界を示しています。data_table関数は主にプランナーの不透明であり、ストアド プロシージャのプランを一緒に折りたたむことができないため、取得された行ごとに 2 つの異なるルックアップを強制しています。

さらに悪いことに、別のテーブルのデータに基づいて 1 つのテーブルにインデックスを付けています。これ、追加のみのワークロード (挿入は許可されているが更新は許可されていない) では機能する可能性がありますが、data_table に更新が適用される可能性がある場合は機能しません。data_table のデータが変更されると、インデックスが間違った結果を返すようになります。

このような場合、ほとんどの場合、結合を明示的に記述し、プランナーにデータを取得する最善の方法を見つけてもらう方がよいでしょう。

ここでの問題は、2 番目の列の順序を変更すると、インデックスの有用性が大幅に低下することです (そして、ディスク I/O が大幅に集中します)。一方、data_table に 2 つの異なるインデックスがあり、明示的な結合がある場合、PostgreSQL はこれをより簡単に処理できます。

于 2013-11-02T06:07:59.180 に答える