首页 > 解决方案 > 如何使表用作计数器的行锁?

问题描述

我有一个 API,它使用不同的提供者进行交易,对于每个提供者,我在表中处理一个内部计数器。问题来了,因为我们将收到更多的流量,并且我们有一个重复编号的交易。

我用于计数器的查询是下一个:

public async Task<int> GetNumeration(int id)
{
    var providerNumerationDb = await _dbContextEf
        .providerNumeration
        .SingleOrDefaultAsync(x => x.providerId == id);

    if (providerNumerationDb == null)
    {
        providerNumerationDb = new ProviderNumeration
        {
            providerId = id,
            number = 1,
        };
        await _dbContextEf.AddAsync(providerNumerationDb);
    }
    else
    {
        providerNumerationDb.Number += 1;
    }

    await _dbContextEf.SaveChangesAsync(0);
    _dbContextEf.Entry(providerNumerationDb).State = EntityState.Detached;

    return providerNumerationDb.Number;
}

我想知道在没有性能影响或其他解决方案的情况下制作 Rowlock 的最佳策略。如果您认为使用 StoredProcedure 或其他方式的解决方案更好,它也是有效的,我们可以更改实现。

标签: c#sql-serverdatabaseasp.net-coreentity-framework-core

解决方案


下面是使用SERIALIZABLE事务的示例。为更新获取更新键上的行锁,当 ID 不存在时,为插入获取键范围锁。后者将限制范围内其他插入的并发性,但可能不会成为问题,因为事务将立即提交,释放锁。

CREATE TABLE dbo.Provider(
      ProviderId int NOT NULL CONSTRAINT PK_Provider PRIMARY KEY
    , Number int NOT NULL
);
GO

CREATE PROCEDURE dbo.GetNextProviderNumber
    @ProviderID int
AS
SET NOCOUNT, XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
DECLARE @Number int;
BEGIN TRY

    BEGIN TRAN;

        UPDATE dbo.Provider
        SET @Number = Number += 1
        WHERE ProviderID = @ProviderID;

        IF @@ROWCOUNT = 0
        BEGIN
            SET @Number = 1;
            INSERT INTO dbo.Provider(ProviderID, Number)
                VALUES(@ProviderID, @Number);
        END

    COMMIT;

    SELECT @Number AS Number;

END TRY
BEGIN CATCH

    IF @@TRANCOUNT > 0 ROLLBACK;
    THROW;

END CATCH;
GO

推荐阅读