web-dev-qa-db-ja.com

ハード削除とソフト削除のパフォーマンス

たくさんの活動をすることを意図したこのテーブルを持っています。その中にあるものは、ユーザーアクションがまだ保留中であることを示します。削除すると、アクションが保留中でなくなったことを示します。システムの他の領域はこれに依存しているため、表自体は実際にはシナリオを説明していません。しかし、私が持っている問題は、保留中のアクションが完了したときにテーブルをそのままにして削除する必要があるか、フラグ列を追加して更新する必要があるかです。

挿入された同じ秒にレコードが削除される場合があることに注意してください。 1秒あたり最大100をサポートしたいと思っていますが、それを制限として使いたくありません。

SQL Server 2014 Enterprise Editionを使用しています。

これがテーブルの定義です(すべてのインデックスは、このテーブルを使用する選択クエリに基づいています)。

CREATE TABLE [dbo].[OpenRounds](
    [OpenRoundId] [bigint] IDENTITY(1,1) NOT NULL,
    [UserId] [int] NOT NULL,
    [GameActivityId] [bigint] NOT NULL,
    [VendorId] [int] NOT NULL,
    [Date] [datetime] NOT NULL,
    [UserBonusId] [bigint] NULL,
    [VendorRoundId] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_GamesOpenRounds] PRIMARY KEY CLUSTERED 
(
    [OpenRoundId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE NONCLUSTERED INDEX [IX_GameOpenRoundsUserIdUserBonusId] ON [dbo].[OpenRounds]
(
    [UserId] ASC,
    [UserBonusId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

CREATE NONCLUSTERED INDEX [IX_GameOpenRoundsUserIdVendorIdVendorRoundId] ON [dbo].[OpenRounds]
(
    [UserId] ASC,
    [VendorId] ASC,
    [VendorRoundId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

CREATE NONCLUSTERED INDEX [IX_GameOpenRoundsVendorIdVendorRoundId] ON [dbo].[OpenRounds]
(
    [VendorId] ASC,
    [VendorRoundId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO
5
Matthew Grima

1秒あたり数千のトランザクションをサポートする目的でシステムを設計している場合は、挿入に依存しないキューシステムを設計することをお勧めします。挿入にはコストがかかり、キューテーブルの設計によっては、キューテーブルの最後のページがシステムのスループットレートを制限する「ホットスポット」になる場合があります。

次の設計では、Roundsテーブルを介して最大100,000の同時トランザクションが可能です。

CREATE TABLE dbo.OpenRounds
(
    OpenRoundID BIGINT NOT NULL
        CONSTRAINT PK_GamesOpenRounds 
        PRIMARY KEY CLUSTERED
    , UserID INT NULL
    , GameActivityID BIGINT NULL
    , VendorID INT NULL
    , RoundDate DATETIME NULL
    , UserBonusID BIGINT NULL
    , VendorRoundID NVARCHAR(50) NULL
    , ReferenceCount INT NOT NULL
        CONSTRAINT DF_OpenRounds_ReferenceCount
        DEFAULT ((0))
);

INSERT INTO dbo.OpenRounds (OpenRoundID)
SELECT TOP(100000) /* top 100,000 -> we're creating 100,000 slots */
    rn = ROW_NUMBER() OVER (ORDER BY o1.object_id)
FROM sys.objects o1
    , sys.objects o2
    , sys.objects o3;

GO
CREATE SEQUENCE dbo.RoundSlotSequence
AS INT
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 100000
CYCLE
CACHE 10000;
GO

上記で作成されたシーケンスは、100,000に達するとロールオーバーするように設計されています。これは、dbo.OpenRoundsテーブルに作成した行数と一致します。

OpenRoundsテーブルにメッセージをプッシュするには、次のプロシージャを使用できます。

CREATE PROCEDURE dbo.PushRound
(
    @UserID INT
    , @GameActivityID BIGINT
    , @VendorID INT
    , @RoundDate DATETIME
    , @UserBonusID BIGINT
    , @VendorRoundID NVARCHAR(50)
)
AS
BEGIN
    DECLARE @SlotID INT;
    DECLARE @SequenceID INT;
    DECLARE @TryCount INT = 0;
    SELECT @SequenceID = NEXT VALUE FOR dbo.RoundSlotSequence;
    WHILE @SlotID IS NULL AND @TryCount < 50
    BEGIN
        UPDATE dbo.OpenRounds WITH (ROWLOCK)
        SET ReferenceCount = ReferenceCount + 1
            , @SlotID = OpenRoundID
            , UserID = @UserID
            , GameActivityID = @GameActivityID
            , VendorID = @VendorID
            , RoundDate = @RoundDate
            , UserBonusID = @UserBonusID
            , VendorRoundID = @VendorRoundID
        WHERE ReferenceCount = 0
            AND OpenRoundID = @SequenceID;
        /* If @SlotID IS NULL the slot was not available
            - wait 5 milliseconds before checking again 
            to see if the slot is open
        */
        IF @SlotID IS NULL WAITFOR DELAY '00:00:00.005';
        SET @TryCount += 1;
    END
    IF @SlotID IS NULL
        RETURN 1
    ELSE 
        RETURN @SlotID
END;
GO

このプロシージャは、OpenRoundsテーブルから次のメッセージを取得するために使用できます。通常、これは、処理する行を継続的に検索するループ内で実行されます。

CREATE PROCEDURE dbo.PopRound
(
    @UserID INT OUTPUT
    , @GameActivityID BIGINT OUTPUT
    , @VendorID INT OUTPUT 
    , @RoundDate DATETIME OUTPUT
    , @UserBonusID BIGINT OUTPUT
    , @VendorRoundID NVARCHAR(50) OUTPUT
    , @MaxRetries INT = 2000 /* 10 seconds 
                default maximum wait time */
)
AS
BEGIN
    DECLARE @SlotID INT;
    DECLARE @TryCount INT = 0;
    WHILE @SlotID IS NULL AND @TryCount < @MaxRetries
    BEGIN
        UPDATE dbo.OpenRounds WITH (ROWLOCK)
        SET ReferenceCount = ReferenceCount - 1
            , @SlotID = OpenRoundID
            , @UserID = UserID
            , @GameActivityID = GameActivityID
            , @VendorID = VendorID
            , @RoundDate = RoundDate
            , @UserBonusID = UserBonusID
            , @VendorRoundID = VendorRoundID
        WHERE ReferenceCount > 0;
        IF @SlotID IS NULL WAITFOR DELAY '00:00:00.005';
        SET @TryCount += 1;
    END
END;
GO

この設計は、Chris Adkinによる Exadat.co.uk Super-scaling SQL Serverサイトで見た概念に大まかに基づいています。彼のサイトは、SQL Serverを限界まで押し上げることに関する優れた資料とガイダンスを提供しています。これには、ハードウェアとSQL Serverとの相互作用に関する非常に詳細な詳細が含まれます。私はクリスや彼のウェブサイトとは一切関係ありません。

5
Max Vernon