0

以下のストアド プロシージャを使用して、DB から利用可能なログイン情報をランダムに選択しています。しかし、複数のスレッドが利用可能なログイン情報を取得しようとすると、レコードのタイムスタンプ フィールドを更新しているにもかかわらず、重複したレコードが返されます。

ここで行をロックして、一度返されたレコードが再び返されないようにするにはどうすればよいですか?

パッティング

WITH (ホールドロック、ローロック)

役に立たなかった!

SELECT TOP 1 @uid = [LoginInfoUid]
      FROM [ZPer].[dbo].[LoginInfos]
      WITH (HOLDLOCK, ROWLOCK)
      WHERE ([Type] = @type)

... ... ...


ALTER PROCEDURE [dbo].[SelectRandomLoginInfo] 
    -- Add the parameters for the stored procedure here
    @type int = 0,
    @expireTimeout int = 86400 -- 24 * 60 * 60 = 24h
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    DECLARE @processTimeout int = 10 * 60

    DECLARE @uid uniqueidentifier

    BEGIN TRANSACTION

    -- SELECT [LoginInfos] which are currently not being processed ([Timestamp] is timedout) and which are not expired.
    SELECT TOP 1 @uid = [LoginInfoUid]
      FROM [MyDb].[dbo].[LoginInfos]
      WITH (HOLDLOCK, ROWLOCK)
      WHERE ([Type] = @type) AND ([Uid] IS NOT NULL) AND ([Key] IS NOT NULL) AND
      (
        ([Timestamp] IS NULL OR DATEDIFF(second, [Timestamp], GETDATE()) > @processTimeout) OR
        (
          DATEDIFF(second, [UpdateDate], GETDATE()) <= @expireTimeout OR
          ([UpdateDate] IS NULL AND DATEDIFF(second, [CreateDate], GETDATE()) <= @expireTimeout)
        )
      )
      ORDER BY NEWID()

    -- UPDATE the selected record so that it won't be re-selected.
    UPDATE [MyDb].[dbo].[LoginInfos] SET
      [UpdateDate] = GETDATE(), [Timestamp] = GETDATE()
      WHERE [LoginInfoUid] = @uid

    -- Return the full record data.
    SELECT *
      FROM [MyDb].[dbo].[LoginInfos]
      WHERE [LoginInfoUid] = @uid

    COMMIT TRANSACTION
END
4

1 に答える 1

8

共有モードで行をロックしても、複数のスレッドが同じ行を読み取るのを防ぐのには少し役立ちません。XLOCKヒントで行を排他的にロックしたい。また、候補行を決定する非常に低い精度のマーカー ( GETDATE3 ミリ秒の精度) を使用しているため、多くの誤検知が発生します。processingビット ( 0 または 1)などの正確なフィールドを使用する必要があります。

最終的には をキューとして扱っているので、 Using tables as QueuesLoginsInfoを読むことをお勧めします。あなたが望むものを達成する方法は、を使用することです。ただし、ランダムなログインを選択するという追加の要件があり、すべてが混乱してしまいます。本当に、本当に、100% ランダム性が必要だと確信していますか? これは非常に珍しい要件であり、適切でパフォーマンスの高いソリューションを見つけるのに苦労することになります。重複が発生し、翌日まで行き詰まります。UPDATE ... WITH OUTPUT

最初の試行は次のようになります。

with cte as (
 select top 1 ...
   from [LoginInfos] with (readpast)
   where processing = 0 and ...

  order by newid())
update cte
   set processing = 1
   output cte...

しかし、このNEWID注文ではテーブル全体のスキャンと並べ替えを行って幸運な勝者の行を 1 つ選択する必要があるため、1) 非常にパフォーマンスが低下し、2) 常にデッドロック状態になります。

今、あなたはこのランダムなフォーラムの暴言を受け入れるかもしれませんが、たまたま私は数年前から SQL Server でバックアップされたキューを使用しており、あなたが望むものが機能しないことを知っています。要件、特にランダム性を変更する必要があります。その後、上記のリンク先の記事に戻って、真のテスト済みスキームのいずれかを使用できます。

編集

ランダム性が必要ない場合は、どういうわけか簡単です。キューとしてのテーブルの問題の要点は、出力行をシークする必要があることです。絶対にスキャンすることはできません。キューのスキャンは実行されないだけでなく、キューの使用方法 (すべてのスレッドが同じ行を必要とする非常に同時のデキュー操作) のためにデッドロックが保証されます。これを実現するには、WHERE 句を sarg-able にする必要があります。これは、1) WHERE 句の式と 2) クラスター化インデックス キーの影響を受けます。式にOR条件を含めることはできないため、すべてを緩めIS NULL OR ...、フィールドを null 非許容に変更し、常に入力します。第二に、インデックスにやさしい方法で比較する必要があります。DATEDIFF(..., field, ...) < @variable)field < DATEDIDD (..., @variable, ...)ただし、2 番目の形式は SARG 対応であるため、常に使用してください。そして、2 つのフィールドのいずれかを選択する必要があります。[Timestamp]または[UpdateDate]、両方を求めることはできません。もちろん、これらはすべて、アプリケーションでより厳密で厳密なステート マシンを必要としますが、それは良いことです。緩い条件と OR 句は、不十分なデータ入力を示すだけです。

select @now = getdate();
select @expired = dateadd(second, @now, @processTimeout);

with cte as (
      select * 
      from [MyDb].[dbo].[LoginInfos] WITH (readpast, xlock)
      WHERE 
          [Type] = @type) AND
          [Timestamp] < @expired)
update cte
    set [Timestamp] = @now
     output INSERTED.*;

これが機能するには、テーブルのクラスター化インデックスがオンになっている必要があります([Type], [Timestamp])(これは、主キーLoginInfoIdを非クラスター化インデックスにすることを意味します)。

于 2011-04-16T18:51:24.477 に答える