3

マルチスレッド アプリケーションで使用される postgreql 接続プールがあり、いくつかのレコードを大きなテーブルに永続的に挿入します。したがって、10 個のデータベース接続があり、同じ関数を実行して、レコードを挿入するとします。

問題は、結果として 10 個のレコードが挿入されていることです。一方、トランザクションのみが互いのレコードを確認できる場合、挿入されるのは 2 ~ 3 個のレコードのみである必要があります (関数は、日付に従ってレコードを挿入しないという決定を下します)。最後に見つかったレコード)。

func の実行期間中は、テーブルをロックする余裕がありません。新しいレコードが並列トランザクションで作成されているにもかかわらず、データベースが新しいレコードにすぐにルールを適用するようにさまざまな手法を試しましたが、まだ成功していません。

だから、私はどんな助けやアイデアにもとても感謝しています!

より具体的には、コードは次のとおりです。

schm.events ( evtime TIMESTAMP, ref_id INTEGER, param INTEGER, type INTEGER);

レコード フィルタ ルール:

BEGIN
select count(*) into nCnt
from events e
where e.ref_id = ref_id and e.param = param and e.type = type 
and e.evtime between (evtime - interval '10 seconds') and (evtime + interval '10 seconds')

if nCnt = 0 then 
  insert into schm.events values (evtime, ref_id, param, type);
end if;
END;

更新 (コメントの長さは残念ながら十分ではありません)

独自のインデックス ソリューションを本番環境に適用しました。結果はかなり受け入れられるものですが、当初の目標は達成されていません。問題は、一意のハッシュでは、連続する hash_codes を持つ 2 つのレコード間の間隔を制御できないことです。

コードは次のとおりです。

CREATE TABLE schm.events_hash (
  hash_code bigint NOT NULL
);
CREATE UNIQUE INDEX ui_events_hash_hash_code ON its.events_hash
  USING btree (hash_code);


--generate the hash codes data by partioning(splitting) evtime in 10 sec intervals:
INSERT into schm.events_hash 
select distinct ( cast( trunc( extract(epoch from evtime) / 10 ) || cast( ref_id as TEXT) || cast( type as TEXT ) || cast( param as TEXT ) as bigint) )
from schm.events;

--and then in a concurrently executed function I insert sequentially:
begin
INSERT into schm.events_hash values ( cast( trunc( extract(epoch from evtime) / 10 ) || cast( ref_id as TEXT) || cast( type as TEXT ) || cast( param as TEXT ) as bigint) );
insert into schm.events values (evtime, ref_id, param, type);
end;

その場合、evtime がハッシュで決定された間隔内にある場合、1 つのレコードのみが挿入されます。場合によっては、決定された異なる間隔を参照しているが、互いに近い (60 秒未満の間隔) レコードをスキップできます。

insert into schm.events values ( '2013-07-22 19:32:37', '123', '10', '20' ); --inserted, test ok, (trunc( extract(epoch from cast('2013-07-22 19:32:37' as timestamp)) / 10 ) = 137450715 )
insert into schm.events values ( '2013-07-22 19:32:39', '123', '10', '20' ); --filtered out, test ok, (trunc( extract(epoch from cast('2013-07-22 19:32:39' as timestamp)) / 10 ) = 137450715 )
insert into schm.events values ( '2013-07-22 19:32:41', '123', '10', '20' ); --inserted, test fail, (trunc( extract(epoch from cast('2013-07-22 19:32:41' as timestamp)) / 10 ) = 137450716 )

最初の目標を達成するためにハッシュ関数を変更する方法があるに違いないと思いますが、まだ見つけていません。おそらく、postgresql 自体によってトランザクションから実行されるいくつかのテーブル制約式があるのでしょうか?

4

1 に答える 1

3

あなたの唯一のオプションは次のとおりです。

  • ハックで一意のインデックスを使用して、20 秒の範囲を単一の値に折りたたみます。

  • アドバイザリ ロックを使用して通信を制御する。また

  • SERIALIZABLE分離し、意図的にセッション間の相互依存関係を作成します。これがあなたの場合に実用的であると100%確信しているわけではありません。

本当に欲しいのはダーティ リードですが、PostgreSQL はダーティ リードをサポートしていないため、そこに行き詰まっています。

要件を管理するために、データベース外のコーディネーターが必要になる場合があります。

一意のインデックス

一意性チェックの目的でタイムスタンプを切り捨て、通常の境界に丸めて、20 秒のチャンクにジャンプすることができます。次に、それらを の一意のインデックスに追加します(chunk_time_seconds(evtime, 20), ref_id, param, type)

1 つの挿入だけが成功し、残りはエラーで失敗します。BEGIN ... EXCEPTIONPL/PgSQLのブロックでエラーをトラップするか、できればアプリケーションでエラーを処理することができます。

の合理的な定義は次のようになると思いchunk_time_secondsます。

CREATE OR REPLACE FUNCTION chunk_time_seconds(t timestamptz, round_seconds integer)
RETURNS bigint
AS $$
SELECT floor(extract(epoch from t) / 20) * 20;
$$ LANGUAGE sql IMMUTABLE;

アドバイザリ ロックの出発点:

アドバイザリ ロックは、単一の bigint または 32 ビット整数のペアで取得できます。キーはそれよりも大きく、3 つの整数であるため、次の最も単純なアプローチを直接使用することはできません。

IF pg_try_advisory_lock(ref_id, param) THEN
   ... do insert ...
END IF;

10 秒後、同じ接続で (必ずしも同じトランザクションである必要はありません) を発行しpg_advisory_unlock(ref_id_param)ます。

フィルターも適用する必要がありtype、 の 3 つの整数引数形式がないため、これは機能しませんpg_advisory_lock。を smallints に変換できる場合は、次のことができparamます。type

IF pg_try_advisory_lock(ref_id, param << 16 + type) THEN

しかし、そうでなければ、あなたは少し窮地に陥っています。もちろん、値をハッシュすることもできますが、ハッシュ衝突の場合にスキップしてはならない挿入を誤ってスキップする (小さな) リスクが発生します。競合する行が表示されないため、再チェックをトリガーする方法がないため、行を比較するだけの通常のソリューションは使用できません。

したがって...キーを64ビットに収めることができ、アプリケーションが同じ接続でロックを解放する前に10〜20秒間ロックを保持する必要性に対処できる場合、アドバイザリロックが機能し、オーバーヘッドが非常に低くなります.

于 2013-08-05T16:22:15.683 に答える