Tenho uma tabela com uma PK e um índice único não clusterizado, conforme abaixo:
CREATE TABLE Table1
(
Id INT IDENTITY(1,1) NOT NULL,
Field1 VARCHAR(25) NOT NULL,
Field2 VARCHAR(25) NULL,
CONSTRAINT PK_Table1 PRIMARY KEY CLUSTERED (Id ASC)
)
CREATE UNIQUE NONCLUSTERED INDEX IX_Field1_Field2 ON Table1
(
Field1 ASC,
Field2 ASC
)
WITH
(
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
IGNORE_DUP_KEY = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
)
Eu tenho 2 trabalhos cujos tempos de execução eu acho que estão se sobrepondo. Ambos incluem o mesmo INSERT
nesta tabela e frequentemente o trabalho que começa por último falha porque tenta inserir um registro Table1
com um valor de chave de índice duplicado.
INSERT Table1
SELECT Field1, Field2
FROM SomeOtherTable sot WITH (NOLOCK)
WHERE NOT EXISTS (
SELECT 1
FROM Table1 t1
WHERE sot.Field1 = t1.Field1
AND sot.Field2 = t1.Field2
)
Pelo que pude discernir, o INSERT
in Job1 ainda está em execução quando o NOT EXISTS
from Job2 é avaliado, resultando em Job2 tentando inserir um valor de chave duplicado. Me parece que o locking Table1
for não está acontecendo conforme o esperado.
Não sei por que isso está acontecendo. Isso teria algo a ver com a NOLOCK
dica usada no INSERT
? Não pensei que essa dica incluiria Table1
em seu escopo, apenas arquivos SomeOtherTable
.
Sei que posso atenuar o erro de chave duplicada definindo IGNORE_DUP_KEY
para ON
o índice, e isso seria bom para nós nessa situação. Gostaria de saber, porém, por que a duplicata está aparecendo no 2º INSERT.
Há muito o que entender se você quiser entender completamente todas as complexidades dos níveis de bloqueio e isolamento no SQL Server, e sua pergunta está essencialmente levando você nessa direção.
Todas as informações estão disponíveis em http://msdn.microsoft.com/en-us/library/ms173763.aspx , embora não seja uma leitura fácil. Uma leitura mais fácil está disponível em http://databases.about.com/od/sqlserver/a/isolationmodels.htm - preste atenção especial à seção "leituras fantasmas".
Para o que você está tentando fazer, você precisaria de AMBAS as transações em execução com um nível de isolamento de "SERIALIZABLE". O que isso basicamente significa é que eles serão executados "como se" um fosse executado após o outro - que eles foram realmente serializados.
O que está acontecendo é que AMBAS as transações decidem que o mesmo registro precisa ser inserido e AMBAS tentam inserir esse mesmo registro. O bloqueio que é levado pela lógica de teste é separado do bloqueio que é levado para a inserção, o que permite que isso aconteça. SERIALIZABLE impedirá que isso aconteça com sua cláusula "Outras transações não podem inserir novas linhas com valores de chave que cairiam no intervalo de chaves lidas por qualquer instrução na transação atual até que a transação atual seja concluída". Outra maneira de pensar nisso é que os bloqueios de leitura do teste precisam ser mantidos até que toda a transação seja concluída. Isso pode ser especificado usando uma dica de consulta HOLDLOCK; mas devo dizer que não joguei pessoalmente com HOLDLOCK, mas apenas com o "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"