9

ユーザーアクションの数を保持するテーブルがあります。アクションが実行されるたびに、値を増やす必要があります。ユーザーは同時に複数のセッションを持つことができるため、マルチユーザーの問題を回避するには、プロセスをアトミックにする必要があります。

テーブルには 3 つの列があります。

  • ActionCodeなのでvarchar
  • UserIDなのでint
  • Countなのでint

行が存在しない場合は新しい行を追加し、count を 1 に設定する関数に ActionCodeandを渡したいと思います。行が存在する場合は、カウントを 1 つ増やします。このテーブルのプライマリ ユニーク インデックスを構成します。UserIDActionCodeUserID

更新するだけでよい場合は、次のような単純なことを行うことができます (UPDATEクエリは既にアトミックであるため)。

UPDATE (Table)
SET Count = Count + 1 
WHERE ActionCode = @ActionCode AND UserID = @UserID

SQL のアトミック トランザクションは初めてです。この質問はおそらくここで複数の部分で回答されていますが、それらを見つけたり、それらの部分を1つのソリューションに配置したりするのに苦労しています. これらのアクションは頻繁に発生する可能性があるため、複雑になることなく、これも非常に高速である必要があります。

編集:申し訳ありませんが、これは単一のクエリで存在する場合のインクリメントを行うMySQLの重複かもしれません。私はたくさん検索しましたがtsql、私の検索では、sql代わりに変更すると、それがトップの結果でした. それがアトミックかどうかは明らかではありませんが、そうであることは間違いありません。この質問と回答によって新しい価値が追加される可能性があると誰かが考えない限り、私はおそらくこれをだまされて削除することに投票します。

4

3 に答える 3

10

SQL Server を使用していると仮定すると、単一のアトミック ステートメントを作成するには、 MERGEを使用できます。

MERGE YourTable AS target
USING (SELECT @ActionCode, @UserID) AS source (ActionCode, UserID)
ON (target.ActionCode = source.ActionCode AND target.UserID = source.UserID)
WHEN MATCHED THEN 
    UPDATE SET [Count] = target.[Count] + 1
WHEN NOT MATCHED THEN   
    INSERT (ActionCode, UserID, [Count])
    VALUES (source.ActionCode, source.UserID, 1)
OUTPUT INSERTED.* INTO #MyTempTable;

UPDATE必要に応じて出力を使用して値を選択します。コードが更新されました。

于 2012-12-03T23:03:23.093 に答える
3

おそらく、SQL Server 2008 で MERGE を使用するのが最善の方法です。それを解決する別の簡単な方法もあります。

UserID/Action が存在しない場合は、 に 0 を指定して新しい行の INSERT を実行しCountます。このステートメントが既に存在するために失敗した場合 (別の同時セッションによって挿入されたとき)、エラーを無視してください。

エラーの可能性を排除するために、実行中に挿入とブロックを実行したい場合は、いくつかのロックヒントを追加できます。

INSERT dbo.UserActionCount (UserID, ActionCode, Count)
SELECT @UserID, @ActionCode, 0
WHERE NOT EXISTS (
   SELECT *
   FROM dbo.UserActionCount WITH (ROWLOCK, HOLDLOCK, UPDLOCK)
   WHERE
      UserID = @UserID
      AND ActionCode = @ActionCode
);

次に、通常の場合と同様に + 1 で UPDATE を実行します。問題が解決しました。

DECLARE @NewCount int,

UPDATE UAC
SET
   Count = Count + 1,
   @NewCount = Count + 1
FROM dbo.UserActionCount UAC
WHERE
   ActionCode = @ActionCode
   AND UserID = @UserID;

注 1: MERGE は問題ないはずですが、何かが 1 つのステートメントで実行される (したがってアトミックである) からといって、同時実行性の問題がないわけではないことを知っておいてください。ロックは、クエリの実行の存続期間中、時間の経過とともに取得および解放されます。次のようなクエリでは、同時実行の問題が発生し、アトミックであるにもかかわらず、ID の挿入が重複して試行されます。

INSERT T
SELECT (SELECT Max(ID) FROM Table) + 1, GetDate()
FROM Table T;

注 2: 超高トランザクション システムの経験者が読んだ記事によると、ロックを取得して解放するよりも、「試してからエラーを処理する」方法の方が高い同時実行性を提供することがわかりました。これはすべてのシステム設計に当てはまるわけではありませんが、少なくとも検討する価値はあります。それ以来、この記事を何度か (今も含めて) 検索しましたが、再び見つけることができませんでした. いつか見つけて読み直したいと思っています.

于 2012-12-03T23:21:52.453 に答える
1

他の誰かがストアド プロシージャでこれを使用し、挿入/更新された行を返す構文が必要な場合に備えて (inserted.* も更新された行を返すことに驚きましたが、そうです)。これが私が最終的に得たものです。主キー (ActionKey) に追加の列があることを忘れていました。以下に反映されています。Count のみを返したい場合は、"output insert.Count" を実行することもできます。これはより実用的です。

CREATE PROCEDURE dbo.AddUserAction
(
@Action varchar(30),
@ActionKey varchar(50) = '',
@UserID int
)
AS

MERGE UserActions AS target
USING (SELECT @Action, @ActionKey, @UserID) AS source (Action, ActionKey, UserID)
ON (target.Action = source.Action AND target.ActionKey = source.ActionKey AND target.UserID = source.UserID)
WHEN MATCHED THEN 
    UPDATE SET [Count] = target.[Count] + 1
WHEN NOT MATCHED THEN   
    INSERT (Action, ActionKey, UserID, [Count])
    VALUES (source.Action, source.ActionKey, source.UserID, 1)
output inserted.*;
于 2012-12-04T05:28:16.950 に答える