10

postgresql に、常に更新される配列を含むテーブルがあります。

私のアプリケーションでは、特定のパラメーターがその配列列に存在しない行の数を取得する必要があります。私のクエリは次のようになります。

select count(id) 
from table 
where not (ARRAY['parameter value'] <@ table.array_column)

しかし、行の量とそのクエリの実行量を増やすと (1 秒あたり数回、場合によっては数百または数千回) パフォーマンスが大幅に低下します。これについては完全にはわかりません)。

基本的に私の質問は次のとおりです。

この状況に当てはまる、私が気付いていない既存のパターンはありますか? これには何が最善のアプローチでしょうか?

あなたが私に与えることができる提案は本当にありがたいです.

4

3 に答える 3

5

PostgreSQLは、実際には配列列のGINインデックスをサポートしています。残念ながら、には使用できないようでNOT ARRAY[...] <@ indexed_colあり、GINインデックスはとにかく頻繁に更新されるテーブルには適していません。

デモ:

CREATE TABLE arrtable (id integer primary key, array_column integer[]);

INSERT INTO arrtable(1, ARRAY[1,2,3,4]);

CREATE INDEX arrtable_arraycolumn_gin_arr_idx
ON arrtable USING GIN(array_column);

-- Use the following *only* for testing whether Pg can use an index
-- Do not use it in production.
SET enable_seqscan = off;

explain (buffers, analyze) select count(id) 
from arrtable 
where not (ARRAY[1] <@ arrtable.array_column);

残念ながら、これは書かれているようにインデックスを使用できないことを示しています。条件を否定しない場合は、それを使用できるため、検索要素を含む行を検索してカウントできます(を削除することによりNOT

インデックスを使用して、ターゲット値を含むエントリをカウントし、その結果をすべてのエントリのカウントから差し引くことができますPostgreSQL(9.1以前)ではテーブル内のすべての行の実行が非常に遅く、順次スキャンが必要なためcount、これは実際には現在のクエリよりも遅くなります。9.2では、bツリーインデックスがオンidになっている場合、インデックスのみのスキャンを使用して行をカウントできる可能性があります。その場合、これは実際には問題ない可能性があります。

SELECT (
  SELECT count(id) FROM arrtable
) - (
  SELECT count(id) FROM arrtable 
  WHERE (ARRAY[1] <@ arrtable.array_column)
);

Pg 9.1以下の元のバージョンよりもパフォーマンスが低下することが保証されています。これは、seqscanに加えて、元のバージョンではGINインデックススキャンも必要になるためです。これを9.2でテストしましたが、カウントにインデックスを使用しているように見えるので、9.2で調べる価値があります。ささいなダミーデータを使用すると、次のようになります。

drop index arrtable_arraycolumn_gin_arr_idx ;
truncate table arrtable;
insert into arrtable (id, array_column)
select s, ARRAY[1,2,s,s*2,s*3,s/2,s/4] FROM generate_series(1,1000000) s;
CREATE INDEX arrtable_arraycolumn_gin_arr_idx
ON arrtable USING GIN(array_column);

このようなGINインデックスは、更新の速度を大幅に低下させ、そもそも作成に非常に時間がかかることに注意してください。テーブルのように、大幅に更新されるテーブルには適していません。

さらに悪いことに、このインデックスを使用するクエリは、元のクエリの最大2倍、同じデータセットで最大で半分の時間がかかります。ARRAY[1]元のクエリの-4秒対2秒のように、インデックスがあまり選択的でない場合は最悪です。インデックスが非常に選択的である場合(つまり、のように一致が多くない場合ARRAY[199])、元の3秒に対して約1.2秒で実行されます。このインデックスは、このクエリに使用する価値がありません。

ここでのレッスンは?場合によっては、正しい答えは単にシーケンシャルスキャンを実行することです。

ヒット率には影響しないため、@ debenhurが提案するようにトリガーを使用してマテリアライズドビューを維持するか、配列を反転してエントリにないパラメーターのリストにして GiSTインデックスを次のように使用できるようにします。 @maniekが提案します。

于 2012-10-26T00:30:20.570 に答える
4

この状況に当てはまる、私が知らない既存のパターンはありますか?このための最良のアプローチは何でしょうか?

この状況での最善の策は、スキーマを正規化することかもしれません。配列をテーブルに分割します。プロパティのテーブルにb-treeインデックスを追加するか、主キーを並べ替えて、で効率的に検索できるようにしproperty_idます。

CREATE TABLE demo( id integer primary key );
INSERT INTO demo (id) SELECT id FROM arrtable;
CREATE TABLE properties (
  demo_id integer not null references demo(id),
  property integer not null,
  primary key (demo_id, property)
);
CREATE INDEX properties_property_idx ON properties(property);

次に、プロパティをクエリできます。

SELECT count(id) 
FROM demo 
WHERE NOT EXISTS (
  SELECT 1 FROM properties WHERE demo.id = properties.demo_id AND property = 1
)

これは元のクエリよりもはるかに高速であると期待していましたが、実際には同じサンプルデータでもほとんど同じです。元のクエリと同じ2秒から3秒の範囲で実行されます。そこにないものを検索する方が、そこにあるもの検索するよりもはるかに遅い場合も同じ問題です。プロパティを含む行を探している場合は、のseqscanを回避し、一致するIDを直接demoスキャンすることができます。properties

繰り返しになりますが、配列を含むテーブルのseqスキャンも同様に機能します。

于 2012-10-26T02:17:10.667 に答える
2

あなたの現在のデータモデルでは、あなたは運が悪いと思います。データベースがクエリに対して実行する必要があるアルゴリズムを考えてみてください。データを順次スキャンしなければ機能しません。

データの逆を格納するように列を配置できますか (クエリがそうなるようにselect count(id) from table where ARRAY[‘parameter value’] <@ table.array_column) ? このクエリは、gin/gist インデックスを使用します。

于 2012-10-25T19:41:57.997 に答える