Por que UPDLOCK causa SELECTs travar (travar)?

13

Eu tenho um select no SQL SERVER que bloqueia a tabela inteira.

Aqui está o script de configuração (certifique-se de não substituir nada)

USE [master]
GO

IF EXISTS(SELECT 1 FROM sys.databases d WHERE d.name = 'LockingTestDB')
DROP DATABASE LockingTestDB
GO

CREATE DATABASE LockingTestDB
GO

USE [LockingTestDB]
GO
IF EXISTS(SELECT 1 FROM sys.tables t WHERE t.name = 'LockingTestTable')
  DROP TABLE LockingTestTable
GO

CREATE TABLE LockingTestTable (
  Id int IDENTITY(1, 1),
  Name varchar(100),
  PRIMARY KEY CLUSTERED (Id)
)
GO

INSERT INTO LockingTestTable(Name) VALUES ('1')
INSERT INTO LockingTestTable(Name) VALUES ('2')
GO

Abra uma nova janela de consulta e execute a seguinte transação (que tem uma espera nela):

USE [LockingTestDB]
GO

BEGIN TRANSACTION
  SELECT * FROM LockingTestTable t WITH (UPDLOCK, ROWLOCK) WHERE t.Name = '1'
  WAITFOR DELAY '00:01:00'

COMMIT TRANSACTION
--ROLLBACK
GO

USE [master]
GO

E outro que será executado (verifique se eles são executados ao mesmo tempo):

USE [LockingTestDB]
GO

SELECT * FROM LockingTestTable t WITH (UPDLOCK, ROWLOCK) WHERE t.Name = '2'

USE [master]
GO

Você notará que a segunda consulta será bloqueada pela primeira. Pare a primeira consulta e execute o ROLLBACK e a segunda será concluída.

Por que isso está acontecendo?

PS: A adição de um índice não clusterizado (com cobertura total) sobre o nome corrigirá:

USE [LockingTestDB]
GO

CREATE NONCLUSTERED INDEX [IX_Name] ON [dbo].[LockingTestTable] 
(
  [Name] ASC
)
INCLUDE ( [Id]) WITH (STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

Novamente por quê?

marius-O
fonte

Respostas:

19

Conforme documentado nos Manuais Online , UPDLOCKrecebe os bloqueios de atualização e os mantém até o final da transação.

Sem um índice para localizar as linhas a serem bloqueadas, todas as linhas testadas são bloqueadas e os bloqueios nas linhas qualificadas são mantidos até a transação ser concluída.

A primeira transação mantém um bloqueio de atualização na linha em que name = 1. A segunda transação é bloqueada quando tenta adquirir um bloqueio de atualização na mesma linha (para testar se name = 2 para essa linha).

Com um índice, o SQL Server pode localizar e bloquear rapidamente apenas as linhas qualificadas, para que não haja conflito.

Você deve revisar o código com um profissional de banco de dados qualificado para validar o motivo da dica de bloqueio e para garantir a presença de índices apropriados.

Informações relacionadas: Modificações de Dados em Isolamento de Captura Instantânea Confirmada de Leitura

Paul White 9
fonte