Eu queria tornar a consulta antiga a seguir segura contra corridas de dados ao executá-las em paralelo. A consulta verifica se existe uma linha em uma tabela usando critérios específicos e se tal linha não existir, insere uma nova com novos dados. A consulta antiga está colada abaixo:
BEGIN
DECLARE @txtPer VARCHAR(MAX) = @nro;
DECLARE @txtCmin VARCHAR(MAX) = @min;
DECLARE @txtCmax VARCHAR(MAX) = @max;
IF NOT EXISTS (SELECT 1 FROM SEND WHERE MSG LIKE (@txtPer + '%') AND STATE < 2 AND ID = 1)
BEGIN
----Time of check is not time of use
----Someone could possibly do another insert before this == data race
INSERT INTO SEND (SNDID, ID, MSGCODE, MSG, STATE, INFO, INFO_TEXT, CHANGEDATE, CREATEDATE)
SELECT MAX(SNDID)+10,1,1,(@txtPer + @txtCmin + ' ' + @txtCmax),0,0,' ',getdate(),getdate() FROM SEND
END
END
Eu criei uma nova versão que é um pouco mais limpa e usa bloqueio de tabela exclusivo:
BEGIN TRANSACTION;
DECLARE @txtPer VARCHAR(MAX) = @nro;
DECLARE @txtCmin VARCHAR(MAX) = @min;
DECLARE @txtCmax VARCHAR(MAX) = @max;
DECLARE @IdMax INT
--Get MAX and simultaneously acquire lock for the table to prevent modifications during this transaction?
SELECT @IdMax=MAX(SNDID) FROM SEND WITH(TABLOCKX)
IF NOT EXISTS(SELECT 1 FROM SEND WHERE MSG LIKE (@txtPer + '%') AND STATE < 2 AND ID = 1)
BEGIN
INSERT INTO SEND (SNDID, ID, MSGCODE, MSG, STATE, INFO, INFO_TEXT, CHANGEDATE, CREATEDATE)
VALUES((@IdMax + 10),1,1,(@txtPer + @txtCmin + ' ' + @txtCmax),0,0,' ',getdate(),getdate())
END
COMMIT; --writes potential change and releases table lock?
Meu entendimento está correto de que o bloqueio de tabela adquirido durante SELECT MAX()
a operação (e possivelmente outros na mesma transação) é mantido até que toda a transação seja finalizada com a COMMIT
instrução? DB é um antigo MS SQL Server 2005.
Sim, desde que seja
TABLOCKX
e nãoTABLOCK
. Isso só funciona se você usar bloqueio pessmista, e não bloqueio de instantâneo otimista, conforme mencionado por @DavidBrowne.No entanto, isso é extremamente ineficiente.
UPDLOCK
eSERIALIZABLE
(ouHOLDLOCK
) dicas.SERIALIZABLE
dica é necessária para bloquear até mesmo a inexistência dos dados (ou seja, um bloqueio de intervalo), garantindo que ninguém mais possa inserir.UPDLOCK
dica é necessária para colocar bloqueios em U na mesa, o que evita que qualquer outra pessoa que solicite um bloqueio em U prossiga, ao mesmo tempo que permite consultas somente leitura. Isso tende a evitar conflitos no nível de isolamento serializável.ID, MSG, STATE
colunas, ou noID, STATE, MSG
, para queNOT EXISTS
seja eficiente.SNDID
valor máximo em toda a tabela; em vez disso, você deve usar umaIDENTITY
coluna.XACT_ABORT ON
para garantir que a reversão ocorra corretamente em caso de erros.Sim. Mas esse bloqueio não impedirá que outra sessão leia a tabela se o banco de dados estiver configurado com READ COMMITTED SNAPSHOT.
A maneira correta de serializá-los é com uma dica de bloqueio no select que decide se a linha existe, assim:
SELECT 1 FROM SEND WITH(UPDLOCK,HOLDLOCK)...