私は単純なメッセージング プログラムを作成しています。メッセージのテーブルがあり、ユーザーが要求し、そのユーザーが処理を行うことができます。どのユーザーが特定のメッセージを主張するかは決まっていないので、私が持っているすべての利用可能なメッセージの最初のものを選択するクエリが必要です。問題は、2 人のユーザーが同時にそれを使用して同じメッセージを主張したくないため、プログラムに戻って次に何を実行するかを調べることなく、2 つのステートメントを連続して実行したいことです。ステートメントの間。セミコロンで区切ることで 2 つの連続したステートメントを実行できると思いますが、最初のクエリで返されたデータを 2 番目のクエリの一部として使用したいと考えています。変数は完璧ですが、私の知る限り、SQL には存在しません。
5 に答える
これが BEGIN TRAN と COMMIT TRAN の目的です。保護したいステートメントをトランザクション内に配置します。
クエリ間で状態を保持する方法はありますか?
いいえ。SQL は手続き型言語ではありません。2 つのクエリを 1 つのクエリとして書き直すことができます (常に可能であるとは限りません。可能であっても価値がないことがよくあります)。手続き型言語でそれらを結合することもできます。多くの SQL サーバーには、これ (「ストアド プロシージャ」) 用の組み込み言語が用意されています。または、アプリケーションで実行することもできます。
問題は、2 人のユーザーが同時に使用して同じメッセージを主張したくないことです。
ロックを使用します。どの SQL サーバーを使用しているかはわかりませんがSELECT ... FOR UPDATE
、それが利用可能であれば、それを使用することはまさにあなたが望むものであるように思えます。
SQL自体には変数がありませんが、(ほぼ?)すべてのRDBMSSQL拡張機能には変数があります。しかし、それだけであなたの問題がどのように解決されるかはよくわかりません。
前述のように、トランザクションはトリックを実行します-2つの無関係なステートメントを効果的にグループ化します。ただし、デフォルトのトランザクションレベルは機能しません。(ほとんど?)RDBMSサーバーのデフォルトのトランザクションレベルはREADCOMMITTEDです。これは、ユーザー2がユーザー1が読み取ったのと同じ行を読み取ることを妨げるものではありません。そのためには、REPEATABLEREADまたはSERIALIZABLEを使用する必要があります。
これは典型的な並行性の問題です。一般に、これを処理する2つの方法は、悲観的なロックまたは楽観的なチェックです。REPEATABLE READトランザクションは悲観的であり(必要かどうかに関係なくロック費用が発生します)、@@ ROWCOUNTのチェックは楽観的です(動作すると仮定しますが、@@ ROWCOUNT = 0の場合は賢明なことを行います)。
通常、私たちは楽観的(ロックには費用がかかります)を使用し、タイムスタンプまたは読み取られたフィールドの組み合わせを使用して、考えたデータを変更していることを確認します。したがって、私の提案は、rowversionまたはtimestampフィールドを含め、それをUPDATEステートメントに戻すことです。次に、@@ ROWCOUNTをチェックして、レコードを更新したかどうかを確認します。そうでない場合は、戻って別のメッセージを選択してください。擬似コードの場合:
int messageId, byte[] rowVersion = DB.Select(
"SELECT TOP 1
MessageId, RowVersion
FROM Messages
WHERE
User IS NULL";
int rowsAffected = DB.Update(
"UPDATE Messages SET
User = @myUserId
WHERE
MessageId = @messageId
AND RowVersion = @rowVersion",
myUserId, messageId, rowVersion
);
if (rowsAffected = 0)
throw new ConcurrencyException("The message was taken by someone else");
特定のステートメントによっては、UPDATEステートメントで「UserIdISNULL」WHERE句を繰り返すだけで解決できる場合があります。これはBrimstedtのソリューションに似ていますが、行が実際に更新されたかどうかを確認するには、 @@ROWCOUNTをチェックする必要があります。
le dorfier が言うように、トランザクションは良い方法ですが、別の方法もあります。
最初に更新を行うことができます。つまり、メッセージにユーザー ID などのタグを付けます。使用しているSQLフレーバーについては言及していませんが、mysqlでは、次のようになると思います。
UPDATE message
SET user_id = ...
WHERE user_id = 0 -- Ensures no two users gets the same message
LIMIT 1
ms sql では、次のようなものになります。
WITH q AS (
SELECT TOP 1
FROM message m
WHERE user_id = 0
)
UPDATE q
SET user_id = 1
/B
おそらく一時テーブルを使用できます。