sql >> Databasteknik >  >> RDS >> Sqlserver

Hur man får nästa nummer i en sekvens

Om du inte underhåller ett bänkbord finns det två alternativ. Inom en transaktion väljer du först MAX(seq_id) med ett av följande tabelltips:

  1. WITH(TABLOCKX, HOLDLOCK)
  2. WITH(ROWLOCK, XLOCK, HOLDLOCK)

TABLOCKX + HOLDLOCK är lite överdrivet. Det blockerar vanliga urvalssatser, som kan anses vara tunga även om transaktionen är liten.

En ROWLOCK, XLOCK, HOLDLOCK bordstips är förmodligen en bättre idé (men:läs alternativet med ett motbord längre fram). Fördelen är att den inte blockerar vanliga select-satser, dvs när select-satserna inte visas i en SERIALIZABLE transaktion, eller när urvalssatserna inte ger samma tabelltips. Använd ROWLOCK, XLOCK, HOLDLOCK kommer fortfarande att blockera infoga uttalanden.

Naturligtvis måste du vara säker på att inga andra delar av ditt program väljer MAX(seq_id) utan dessa tabelltips (eller utanför en SERIALIZABLE transaktion) och använd sedan detta värde för att infoga rader.

Observera att beroende på antalet rader som är låsta på detta sätt, är det möjligt att SQL Server kommer att eskalera låset till ett tabelllås. Läs mer om låseskalering här .

Infogningsproceduren med WITH(ROWLOCK, XLOCK, HOLDLOCK) skulle se ut som följer:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @max_seq IS NULL SET @max_seq=0;
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

Ett alternativ och förmodligen en bättre idé är att ha en disk tabell och ge dessa tabelltips på bänkbordet. Den här tabellen skulle se ut så här:

CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);

Du skulle sedan ändra insättningsproceduren enligt följande:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @new_seq IS NULL 
        BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
    ELSE
        BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET [email protected]_seq WHERE [email protected]_model; END
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

Fördelen är att färre radlås används (dvs ett per modell i dbo.counter_seq ), och låseskalering kan inte låsa hela dbo.table_seq tabell blockerar alltså valda uttalanden.

Du kan testa allt detta och se effekterna själv genom att placera en WAITFOR DELAY '00:01:00' efter att ha valt sekvensen från counter_seq , och pilla med tabellen/tabellerna i en andra SSMS-flik.

PS1:Använder ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID) är inte ett bra sätt. Om rader raderas/läggs till, eller ID ändras, kommer sekvensen att ändras (tänk på faktura-ID som aldrig bör ändras). Även när det gäller prestanda är det en dålig idé att behöva bestämma radnumren för alla tidigare rader när man hämtar en enda rad.

PS2:Jag skulle aldrig använda externa resurser för att tillhandahålla låsning, när SQL Server redan tillhandahåller låsning genom isoleringsnivåer eller finkorniga tabelltips.



  1. Hur hanterar jag sökfråga med japanska med MySql?

  2. Yii CDbConnection kunde inte öppna DB-anslutningen:kunde inte hitta drivrutinen med google cloud sql

  3. Hur man löser ett för stort undantag XAMPP Mysql

  4. Codeigniter/PHP kontrollera om kan ansluta till databasen