Pokud neudržujete pultový stůl, máte dvě možnosti. V rámci transakce nejprve vyberte MAX(seq_id)
pomocí jedné z následujících tabulkových nápověd:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
je trochu přehnané. Blokuje běžné výběrové příkazy, které lze považovat za těžké i když je transakce malá.
A ROWLOCK, XLOCK, HOLDLOCK
nápověda k tabulce je pravděpodobně lepší nápad (ale:přečtěte si alternativu s protitabulkou dále). Výhodou je, že neblokuje běžné příkazy select, tj. když se příkazy select neobjeví v SERIALIZABLE
transakce, nebo když příkazy select neposkytují stejné rady pro tabulku. Pomocí ROWLOCK, XLOCK, HOLDLOCK
bude stále blokovat příkazy insert.
Samozřejmě si musíte být jisti, že žádná jiná část vašeho programu nezvolí MAX(seq_id)
bez těchto tabulkových rad (nebo mimo SERIALIZABLE
transakce) a poté tuto hodnotu použijte k vložení řádků.
Všimněte si, že v závislosti na počtu řádků, které jsou uzamčeny tímto způsobem, je možné, že SQL Server eskaluje zámek na zámek tabulky. Další informace o eskalaci uzamčení zde .
Procedura vkládání pomocí WITH(ROWLOCK, XLOCK, HOLDLOCK)
bude vypadat následovně:
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
Alternativní a pravděpodobně lepší nápad je mít počítadlo stůl a poskytněte tyto rady k tabulce na pultovém stole. Tato tabulka by vypadala takto:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Potom byste změnili postup vkládání následovně:
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
Výhodou je, že se používá méně řádkových zámků (tj. jeden na model v dbo.counter_seq
) a eskalace zámku nemůže uzamknout celý dbo.table_seq
tabulky a tím blokuje příkazy select.
To vše můžete vyzkoušet a sami vidět účinky umístěním WAITFOR DELAY '00:01:00'
po výběru sekvence z counter_seq
a pohráváte si s tabulkami na druhé kartě SSMS.
PS1:Pomocí ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
není dobrý způsob. Pokud se odstraní/přidají řádky nebo se změní ID, změní se pořadí (vezměte v úvahu ID faktur, která by se nikdy neměla změnit). Také z hlediska výkonu je nutnost určovat čísla všech předchozích řádků při načítání jednoho řádku špatný nápad.
PS2:Nikdy bych nepoužil vnější zdroje k zajištění zamykání, když SQL Server již poskytuje zamykání prostřednictvím úrovní izolace nebo jemných tabulkových rad.