Ich habe eine API, die Transaktionen mit verschiedenen Anbietern durchführt, und für jeden Anbieter verarbeite ich einen internen Zähler in einer Tabelle. Das Problem tritt auf, weil wir mehr Verkehr erhalten und eine Transaktion mit wiederholter Nummerierung haben.
Die Abfrage, die ich für den Zähler verwende, ist die nächste:
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;
}
Ich möchte die beste Strategie kennen, um einen Rowlock ohne Leistungsfolgen oder eine andere Lösung zu erstellen. Wenn Sie der Meinung sind, dass die Lösung beispielsweise mit StoredProcedure oder auf andere Weise besser ist, gilt dies auch. Wir können die Implementierung ändern.
Unten finden Sie ein Beispiel für die Verwendung einer SERIALIZABLE
Transaktion. Eine Zeilenverriegelung für den aktualisierten Schlüssel wird für die Aktualisierung erfasst, und wenn die ID noch nicht vorhanden ist, wird eine Schlüsselbereichssperre für die Einfügung erfasst. Letzteres begrenzt die Parallelität für andere Einfügungen innerhalb des Bereichs, ist jedoch wahrscheinlich kein Problem, da die Transaktion sofort festgeschrieben wird und die Sperren aufgehoben werden.
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