47

ロックされていないPostgresqlの行を選択する方法はありますか?私は次のことを行うマルチスレッドアプリを持っています:

Select... order by id desc limit 1 for update

テーブルの上。

複数のスレッドがこのクエリを実行する場合、両方が同じ行を引き戻そうとします。

1つは行ロックを取得し、他はブロックし、最初のブロックが行を更新した後に失敗します。私が本当に望んでいるのは、2番目のスレッドが、WHERE句に一致し、まだロックされていない最初の行を取得することです。

明確にするために、selectを実行した後、各スレッドが最初に使用可能な行をすぐに更新するようにします。

したがって、の行がある場合はID: 1,2,3,4、最初のスレッドが入り、の行を選択しID=4てすぐに更新します。

ID=3そのトランザクション中に2番目のスレッドが来た場合は、その行を取得してすぐにその行を更新したいと思います。

句がロックされた行と一致するnowaitため、Shareはこれを達成しません。基本的に私が欲しいのは、条項の「ANDNOTLOCKED」のようなものです。WHERE(ID=4 in my example)WHERE

Users

-----------------------------------------
ID        | Name       |      flags
-----------------------------------------
1         |  bob       |        0
2         |  fred      |        1
3         |  tom       |        0
4         |  ed        |        0

クエリが" Select ID from users where flags = 0 order by ID desc limit 1"で、行が返されたときに次のものが " Update Users set flags = 1 where ID = 0"の場合、最初のスレッドで行を取得し、次のスレッドで。ID 4で行を取得しID 3ます。

selectに""を追加For Updateすると、最初のスレッドが行を取得し、2番目のスレッドがブロックして、最初のトランザクションがコミットされるとWHERE句が満たされないため、何も返されません。

""を使用しない場合はFor Update、後続の更新でWHERE句を追加する必要があります(WHEREフラグ= 0)。これにより、1つのスレッドのみが行を更新できます。

2番目のスレッドは最初のスレッドと同じ行を選択しますが、2番目のスレッドの更新は失敗します。

いずれにせよ、トランザクションが重複する最初のスレッドに行4を、2番目のスレッドに行3を与えるデータベースを取得できないため、2番目のスレッドは行の取得と更新に失敗します。

4

14 に答える 14

9

いいえいいえNOOO:-)

著者の意味を知っています。私も同様の状況にあり、素晴らしい解決策を思いつきました。まず、自分の状況を説明することから始めます。特定の時間に送信する必要のあるメッセージを格納するテーブルiがあります。PGは関数のタイミング実行をサポートしていないため、デーモン(またはcron)を使用する必要があります。いくつかの並列プロセスを開くカスタム作成のスクリプトを使用します。すべてのプロセスは、+1秒/-1秒の精度で送信する必要がある一連のメッセージを選択します。テーブル自体は、新しいメッセージで動的に更新されます。

したがって、すべてのプロセスで一連の行をダウンロードする必要があります。この行のセットは、多くの混乱を招くため、他のプロセスではダウンロードできません(1つだけを受信する必要があるときに、カップルのメッセージを受信する人もいます)。そのため、行をロックする必要があります。ロック付きの一連のメッセージをダウンロードするためのクエリ:

FOR messages in select * from public.messages where sendTime >= CURRENT_TIMESTAMP - '1 SECOND'::INTERVAL AND sendTime <= CURRENT_TIMESTAMP + '1 SECOND'::INTERVAL AND sent is FALSE FOR UPDATE LOOP
-- DO SMTH
END LOOP;

このクエリを使用するプロセスは、0.5秒ごとに開始されます。したがって、これにより、次のクエリが最初のロックで行のロックを解除するのを待機することになります。このアプローチでは、大幅な遅延が発生します。NOWAITを使用する場合でも、送信する必要のある新しいメッセージがテーブルにある可能性があるため、クエリによって不要な例外が発生します。単にFORSHAREを使用すると、クエリは適切に実行されますが、それでも大きな遅延が発生するのに多くの時間がかかります。

それを機能させるために、私たちは少し魔法をかけます:

  1. クエリの変更:

    FOR messages in select * from public.messages where sendTime >= CURRENT_TIMESTAMP - '1 SECOND'::INTERVAL AND sendTime <= CURRENT_TIMESTAMP + '1 SECOND'::INTERVAL AND sent is FALSE AND is_locked(msg_id) IS FALSE FOR SHARE LOOP
    -- DO SMTH
    END LOOP;
    
  2. 不思議な関数'is_locked(msg_id)'は次のようになります。

    CREATE OR REPLACE FUNCTION is_locked(integer) RETURNS BOOLEAN AS $$
    DECLARE
        id integer;
        checkout_id integer;
        is_it boolean;
    BEGIN
        checkout_id := $1;
        is_it := FALSE;
    
        BEGIN
            -- we use FOR UPDATE to attempt a lock and NOWAIT to get the error immediately 
            id := msg_id FROM public.messages WHERE msg_id = checkout_id FOR UPDATE NOWAIT;
            EXCEPTION
                WHEN lock_not_available THEN
                    is_it := TRUE;
        END;
    
        RETURN is_it;
    
    END;
    $$ LANGUAGE 'plpgsql' VOLATILE COST 100;
    

もちろん、この関数をカスタマイズして、データベースにある任意のテーブルで機能するようにすることができます。私の意見では、1つのテーブルに対して1つのチェック関数を作成する方が良いと思います。この関数にさらに多くのものを追加すると、遅くなるだけです。とにかくこの句をチェックするのに時間がかかるので、さらに遅くする必要はありません。私にとってこれは完全なソリューションであり、完全に機能します。

これで、50個のプロセスを並行して実行すると、すべてのプロセスに送信する新しいメッセージの一意のセットがあります。送信されたら、sent = TRUEで行を更新し、二度とその行に戻ることはありません。

この解決策があなた(著者)にも役立つことを願っています。ご不明な点がございましたら、お気軽にお問い合わせください:-)

ああ、これがあなたにもうまくいったかどうか教えてください。

于 2010-07-14T02:52:32.287 に答える
6

私は次のようなものを使用します:

select  *
into l_sms
from sms
where prefix_id = l_prefix_id
    and invoice_id is null
    and pg_try_advisory_lock(sms_id)
order by suffix
limit 1;

pg_advisory_unlock を呼び出すことを忘れないでください

于 2010-12-25T14:02:01.967 に答える
5

キューを実装しようとしている場合は、この問題やその他の問題を既に解決している PGQ を参照してください。http://wiki.postgresql.org/wiki/PGQ_Tutorial

于 2010-12-25T18:28:49.467 に答える
1

これは、SELECT ... NOWAIT によって実現できます。例はこちらです。

于 2008-12-23T17:56:01.693 に答える
1

私の解決策は、UPDATE ステートメントを RETURNING 句と共に使用することです。

Users

-----------------------------------
ID        | Name       |      flags
-----------------------------------
1         |  bob       |        0  
2         |  fred      |        1  
3         |  tom       |        0   
4         |  ed        |        0   

SELECT .. FOR UPDATE使用する代わりに

BEGIN; 

UPDATE "Users"
SET ...
WHERE ...;
RETURNING ( column list );

COMMIT;

UPDATE ステートメントはテーブルの更新に対して ROW EXCLUSIVE ロックを取得するため、シリアル化された更新が取得されます。読み取りは引き続き許可されますが、UPDATE トランザクションの開始前のデータのみが表示されます。

参照: Pg ドキュメントの同時実行制御の章。

于 2011-04-11T04:24:21.683 に答える
0

SELECTFORSHAREを探しているようです。

http://www.postgresql.org/docs/8.3/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE

FOR SHAREは、取得された各行で排他的ではなく共有ロックを取得することを除いて、同様に動作します。共有ロックは、他のトランザクションがこれらの行でUPDATE、DELETE、またはSELECT FOR UPDATEを実行するのをブロックしますが、SELECTFORSHAREの実行を妨げることはありません。

特定のテーブルがFORUPDATEまたはFORSHAREで指定されている場合、それらのテーブルからの行のみがロックされます。SELECTで使用される他のテーブルは、通常どおりに読み取られます。テーブルリストのないFORUPDATEまたはFORSHARE句は、コマンドで使用されるすべてのテーブルに影響します。FORUPDATEまたはFORSHAREがビューまたはサブクエリに適用される場合、ビューまたはサブクエリで使用されるすべてのテーブルに影響します。

テーブルごとに異なるロック動作を指定する必要がある場合は、複数のFORUPDATE句とFORSHARE句を記述できます。同じテーブルがFORUPDATE句とFORSHARE句の両方によって言及されている(または暗黙的に影響を受けている)場合、そのテーブルはFORUPDATEとして処理されます。同様に、テーブルに影響を与える句のいずれかで指定されている場合、テーブルはNOWAITとして処理されます。

FORUPDATEおよびFORSHAREは、返された行を個々のテーブル行で明確に識別できないコンテキストでは使用できません。たとえば、集計では使用できません。

于 2008-12-23T17:50:19.300 に答える
0

何を達成しようとしていますか?ロック解除された行の更新も完全なトランザクションもあなたが望むことをしない理由をよりよく説明できますか?

さらに良いことに、競合を防ぎ、各スレッドに異なるオフセットを使用させることができますか?テーブルの関連部分が頻繁に更新されている場合、これはうまく機能しません。まだ衝突が発生しますが、インサートに大きな負荷がかかっている場合に限ります。

Select... order by id desc offset THREAD_NUMBER limit 1 for update
于 2008-12-23T18:00:40.950 に答える
0

^^それはうまくいきます。「ロック済み」の「即時」ステータスにすることを検討してください。

あなたのテーブルがそのようなものだとしましょう:

id | 名前| 姓| 状態

たとえば、考えられるステータスは次のとおりです。1=保留中、2 =ロック済み、3 =処理済み、4 =失敗、5=拒否

すべての新しいレコードは、ステータスが保留中(1)で挿入されます

プログラムは次のように実行します: "update mytable set status = 2 where id =(select id from mytable where name like'%John%' and status = 1 limit 1)returning id、name、surname"

次に、プログラムがその処理を実行し、このスレッドがその行をまったく処理するべきではないという結論に達した場合、「mytable set status = 1 where id =?」を実行します。

それ以外は、他のステータスに更新されます。

于 2009-12-07T12:40:33.897 に答える
0

以下はいかがでしょうか?他の例よりも原子的に扱われる可能性がありますが、私の仮定が間違っていないことを確認するためにテストする必要があります

UPDATE users SET flags = 1 WHERE id = ( SELECT id FROM users WHERE flags = 0 ORDER BY id DESC LIMIT 1 ) RETURNING ...;

同時の UPDATE に直面して、一貫性のある SELECT 結果を提供するために postgres が内部的に使用するロック スキームが何であれ、おそらくまだ行き詰まるでしょう。

于 2008-12-24T17:48:46.637 に答える
0

より良い答えがまだ見つからないため、アプリ内でロックを使用して、このクエリを実行するコードへのアクセスを同期することにしました。

于 2008-12-23T20:02:25.213 に答える
0

マルチスレッドとクラスタで使用されますか?
これはどう?

START TRANSACTION;

// All thread retrive same task list
// If result count is very big, using cursor 
//    or callback interface provied by ORM frameworks.
var ids = SELECT id FROM tableName WHERE k1=v1;

// Each thread get an unlocked recored to process.
for ( id in ids ) {
   var rec = SELECT ... FROM tableName WHERE id =#id# FOR UPDATE NOWAIT;
   if ( rec != null ) {
    ... // do something
   }
}

COMMIT;
于 2011-11-17T07:21:52.323 に答える