2016-08-26 30 views
2

ここではMariaDB/MySQLでのきちんとしたロックの問題があります。SQLテーブルロック競合状態 - SELECT then INSERT

サーバがマルチパートSMSメッセージを再構築しています。メッセージはセグメントに到着します。同じ「smsfrom」と「uniqueid」を持つセグメントは同じメッセージの一部です。セグメントは、1から「セグメント合計」までのセグメント番号を有する。メッセージのすべてのセグメントが到着すると、メッセージは完了です。次のように我々は、再組み立てされるのを待っている比類のないセグメントのテーブルを持っている:

CREATE TABLE frags (
     smsfrom TEXT, 
     uniqueid VARCHAR(32) NOT NULL, 
     smsbody TEXT, 
     segmentnum INTEGER NOT NULL, 
     segmenttotal INTEGER NOT NULL); 

新しいセグメントが入ってきたとき、私たちはトランザクションで、行い、

SELECT ... FROM frags WHERE smsfrom = % AND uniqueid = %; 

これは私たちに受信したすべてのセグメントを取得しますこれまでのところ。新しく にすべてのセグメント番号がある場合は、完全なメッセージが表示されます。 さらに処理するためにメッセージを送信し、関連するフラグメントを削除します。ファイン。

すべてのセグメントがまだ到着していない場合は、直前に取得したセグメントのINSERTを実行します。自動コミットはオフであるため、両方の操作はトランザクションの一部です。偶然にもInnoDBエンジン。

これは競合状態です。 2つのセグメントが同時に2セグメントメッセージのために入り、別々のプロセスによって処理されます。プロセスAはSELECTを実行し、何も見つけません。プロセスBはSELECTを実行し、何も見つけません。プロセスAはセグメント1を挿入しますが、問題はありません。プロセスBはセグメント2を挿入しますが問題ありません。今私たちは立ち往生しています - すべてのセグメントはテーブルにありますが、私たちは気づいていませんでした。メッセージは永遠にそこに残されています。 (実際には、数分おきに古いものを取り除いて無視しますが、無視してください)

何が問題なのですか? SELECTは何も見つからないため、行をロックしません。 まだ存在しない行に対して行ロックが必要です。 SELECTへのFOR UPDATEの追加は役に立ちません。ロックするものは何もありません。また、LOCK IN SHARE MODEもありません。 SERIALIZABLEのトランザクションタイプに行くことは助けになりません。なぜなら、これは単なるグローバルLOCK IN SHAREモードなのですから。

これで、最初にINSERTを実行してから、SELECTを実行してすべてのセグメントがあるかどうかを確認します。プロセスAは1のINSERTを行いますが、問題はありません。プロセスBは2の挿入を行いますが問題ありません。プロセスAはSELECTを行い、1だけを見る。プロセスBはSELECTを実行し、2だけを見る。それは反復可能な読み取りセマンティクスである。いいえ。

ブルートフォースアプローチは、これを行う前にLOCK TABLEです。それはうまくいくはずですが、私は他のテーブルを含むトランザクションにおり、LOCK TABLEはコミットを意味するため、迷惑です。

各INSERTが実行された後にコミットを実行しても、完全にはわかりません。

もっと洗練されたソリューションはありますか?

+0

コミット後に他のセグメントのチェックを移動した場合はどうなりますか?プロセスAとBが両方ともメッセージを処理しないようにするには、何らかの方法が必要です。 – Greg

答えて

1

なぜならば

1)プロセス1.フラグメントテーブルに挿入します。その他

挿入.... コミットします。

2)工程2 これは

選択smsfrom、ユニークな、一意IDによって完全なマルチパートSMSを見つけ、smsfromによってfragsグループから)(カウント、ユニークな、ユニーク有するカウント()== segmenttotal。

= smsfrom = <> fragsとユニークから<を削除して新しいテーブルに

それらを動かし

>。

コミット; SELECTは、断片の完全なセットを返した場合

INSERT ... -- Insert new fragment. 
COMMIT 
SELECT ... FROM frags WHERE smsfrom = % AND uniqueid = % FOR UPDATE; 

チェック:私は上記の書いたよう

+0

私はそのようなことをやめました - 挿入、コミット、そして一つのプロセスでの選択。選択されたFOR UPDATEを実行して別のSELECTに対して行ロックを取得する必要があるか、または2つのプロセスが組み立てられたメッセージを見つけて2回処理される可能性があります。 –

+0

非常にうれしいです。私はOOB(アウトオブバンド)マルチパートのメッセージにあなたが何をするかを聞いてはいけません。オリジナルを再構築した後に到着するマルチパートのメッセージです。 2日以上経過しているもの - ビジネス要件によって異なります。 –

0

は、私はこれをやってしまいました。メッセージを再構成して処理する場合は、

DELETE ... FROM FRAGS WHERE smsfrom = % AND uniqueid = %; 

COMMITとFOR UPDATEの両方が必要です。 COMMITは、各プロセスが別のプロセスからのINSERTを参照するように必要です。 DELETEが完了するまで、すべてのフラグメントを行ロックするために、SELECTにFOR UPDATEが必要です。そうしないと、2つのプロセスがSELECT内のフラグメントの完全なセットを見て、メッセージを2回再アセンブルして処理することがあります。

これは驚くほど1テーブルの問題では複雑ですが、動作するようです。