Hoje descobri que o disco rígido que armazena meus bancos de dados estava cheio. Isso já aconteceu antes, geralmente a causa é bastante evidente. Normalmente, há uma consulta incorreta, que causa grandes vazamentos no tempdb, que crescem até o disco ficar cheio. Desta vez foi um pouco menos evidente o que aconteceu, já que tempdb não foi a causa da unidade cheia, foi o próprio banco de dados.
Os fatos:
- O tamanho normal do banco de dados é de cerca de 55 GB, cresceu para 605 GB.
- O arquivo de log tem tamanho normal, o arquivo de dados é enorme.
- Datafile tem 85% de espaço disponível (eu interpreto isso como 'ar': espaço que foi usado, mas foi liberado. O SQL Server reserva todo o espaço uma vez alocado).
- O tamanho do Tempdb é normal.
Encontrei a causa provável; há uma consulta que seleciona muitas linhas (junção ruim causa a seleção de 11 bilhões de linhas onde são esperadas algumas centenas de milhares). Esta é uma SELECT INTO
consulta, o que me fez pensar se o seguinte cenário poderia ter acontecido:
- SELECT INTO é executado
- A tabela de destino é criada
- Os dados são inseridos conforme são selecionados
- O disco enche, fazendo com que a inserção falhe
- SELECT INTO é abortado e revertido
- A reversão libera espaço (os dados já inseridos são removidos), mas o SQL Server não libera o espaço liberado.
Nesta situação, no entanto, eu não esperava que a tabela criada pelo SELECT INTO
ainda existisse, ela deveria ser descartada pelo rollback. Eu testei isso:
BEGIN TRANSACTION
SELECT T.x
INTO TMP.test
FROM (VALUES(1))T(x)
ROLLBACK
SELECT *
FROM TMP.test
Isto resulta em:
(1 row affected)
Msg 208, Level 16, State 1, Line 8
Invalid object name 'TMP.test'.
No entanto, a tabela de destino existe. A consulta real não foi executada em uma transação explícita, isso pode explicar a existência da tabela de destino?
As suposições que esbocei aqui estão corretas? Este é um cenário provável de ter acontecido?
Sim, exatamente assim.
Se você fizer um simples
select into
fora de umexplicit transaction
, existem doistransactions
no modo autocommit: o primeiro cria otable
e o segundo o preenche.Você pode provar isso para si mesmo desta forma:
Em um servidor de teste dedicado
database
emsimple recovery model
, primeiro faça umcheckpoint
e certifique-se de que o log contenha apenas algumas linhas (3 no caso de 2016) relacionadas aocheckpoint
. Em seguida, execute umaselect into
linha e verifiquelog
novamente, procurando por umbegin tran
associado aselect into
:Você obterá 2 linhas, mostrando que você tinha 2
transactions
.Sim, eles estão corretos.
A
insert
parte deselect into
wasrolled back
, mas não libera nenhum espaço de dados. Você pode verificar isso executandosp_spaceused
; você verá muitosunallocated space
.Se você quiser que o banco de dados libere esse espaço não alocado, você deve usar
shrink
seu(s) arquivo(s) de dados.Você está correto, o
SELECT...INTO
comando não é atômico. Isso não foi documentado no momento da postagem original, mas agora é chamado especificamente na página SELECT - INTO Clause (Transact-SQL) no MS Docs (yay open source!):Vou criar um banco de dados que usa o modelo de recuperação completo. Vou fornecer um arquivo de log bastante pequeno e, em seguida, informar que o arquivo de log não pode crescer automaticamente:
E então tentarei inserir todas as postagens da minha cópia do banco de dados StackOverflow2010. Isso deve gravar um monte de coisas no arquivo de log.
Isso resultou no seguinte erro após a execução por 4 segundos:
Mas há uma tabela Posts vazia no meu novo banco de dados:
Então, como você suspeitava, deu
CREATE TABLE
certo, mas aINSERT
parte foi toda revertida. Uma solução alternativa seria usar uma transação explícita (que você já observou em sua pergunta).