ここでは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が実行された後にコミットを実行しても、完全にはわかりません。
もっと洗練されたソリューションはありますか?
コミット後に他のセグメントのチェックを移動した場合はどうなりますか?プロセスAとBが両方ともメッセージを処理しないようにするには、何らかの方法が必要です。 – Greg