Estendendo a resposta de Marcos ...
Quando ocorre um evento de tempo limite do cliente (.net CommandTimeout, por exemplo), o cliente envia um "ABORT" ao SQL Server. O SQL Server simplesmente abandona o processamento da consulta. Nenhuma transação é revertida, nenhum bloqueio é liberado.
Agora, a conexão é retornada ao pool de conexões, portanto não está fechada no SQL Server. Se isso acontecer (via KILL ou reinicialização do cliente etc), as transações + bloqueios serão apagadas. Observe que sp_reset_connection não os limpa ou não, mesmo que seja anunciado para isso
Esse detrito do aborto irá bloquear outros processos.
A maneira de fazer o SQL Server limpar transações + bloqueios no tempo limite do cliente (estritamente, eventos ABORT) é usar SET XACT_ABORT ON.
Você pode verificar se isso abre duas janelas de consulta no SSMS:
Janela 1:
No menu Query..Query Options, defina um tempo limite de 5 segundos e execute
BEGIN TRAN
UPDATE sometable WITH (TABLOCKX) SET foo = foo WHERE 1 = 0;
WAITFOR DELAY '00:00:10' -- just has to be longer then timeout
Janela 2, isso esperará para sempre (ou atingirá o tempo limite)
SELECT * FROM sometable
SET XACT_ABORT ON também tem efeitos colaterais interessantes:
- @@ TRANCOUNT é definido como zero na reversão implícita, mas o erro 266 é suprimido (isso acontece se @@ TRANCOUNT for diferente na entrada e na saída de um processo armazenado)
- XACT_STATE será -1 (está "condenado")
A combinação disso significa que você não pode usar SAVEPOINTS (embora não me lembre do comportamento exato) para confirmações / reversões parciais. O que combina comigo
Links SO no SET XACT_ABORT:
Em procs armazenados aninhados:
Em sp_reset_connection: