Espacio en disco lleno durante la inserción, ¿qué sucede?


17

Hoy descubrí que el disco duro que almacena mis bases de datos estaba lleno. Esto ha sucedido antes, generalmente la causa es bastante evidente. Por lo general, hay una mala consulta, lo que provoca que se produzcan grandes derrames en tempdb que crece hasta que el disco está lleno. Esta vez fue un poco menos evidente lo que sucedió, ya que tempdb no fue la causa del disco completo, sino la base de datos en sí.

Los hechos:

  • El tamaño habitual de la base de datos es de aproximadamente 55 GB, creció a 605 GB.
  • El archivo de registro tiene un tamaño normal, el archivo de datos es enorme.
  • El archivo de datos tiene un 85% de espacio disponible (interpreto esto como 'aire': espacio que se utilizó, pero se ha liberado. SQL Server reserva todo el espacio una vez asignado).
  • El tamaño de tempdb es normal.

He encontrado la causa probable; hay una consulta que selecciona demasiadas filas (la unión incorrecta provoca la selección de 11 mil millones de filas donde se esperan un par de cientos de miles). Esta es una SELECT INTOconsulta, que me hizo preguntarme si el siguiente escenario podría haber sucedido:

  • SELECT INTO se ejecuta
  • Se crea la tabla de destino
  • Los datos se insertan a medida que se seleccionan
  • El disco se llena, haciendo que la inserción falle
  • SELECT INTO es abortado y revertido
  • La reversión libera espacio (los datos ya insertados se eliminan), pero SQL Server no libera el espacio liberado.

En esta situación, sin embargo, no hubiera esperado que la tabla creada por el SELECT INTOtodavía existiera, debería ser descartada por la reversión. Probé esto:

BEGIN TRANSACTION 
SELECT  T.x
INTO    TMP.test
FROM    (VALUES(1))T(x)

ROLLBACK

SELECT  * 
FROM    TMP.test

Esto resulta en:

(1 row affected)
Msg 208, Level 16, State 1, Line 8
Invalid object name 'TMP.test'.

Sin embargo, la tabla de destino existe. Sin embargo, la consulta real no se ejecutó en una transacción explícita, ¿eso puede explicar la existencia de la tabla de destino?

¿Son correctas las suposiciones que bosquejé aquí? ¿Es probable que esto haya sucedido?

Respuestas:


17

Sin embargo, la consulta real no se ejecutó en una transacción explícita, ¿eso puede explicar la existencia de la tabla de destino?

Sí, exactamente así.

Si hace un simple select intofuera de un explicit transaction, hay dos transactionsen modo de confirmación automática: el primero crea el tabley el segundo lo llena.

Puedes probártelo de esta manera:

En un databaseservidor dedicado en un servidor de prueba simple recovery model, primero realice un checkpointy asegúrese de que el registro contenga solo unas pocas filas (3 en el caso de 2016) relacionadas checkpoint. Luego ejecute select intouno de una fila y verifique lognuevamente, buscando un begin tranasociado con select into:

checkpoint;

select *
from sys.fn_dblog(null, null);

select 'a' as col
into dbo.t3;  

select *
from sys.fn_dblog(null, null)
where Operation = 'LOP_BEGIN_XACT'
      and [Transaction Name] = 'SELECT INTO';

Obtendrá 2 filas, que muestran que tenía 2 transactions.

¿Son correctas las suposiciones que bosquejé aquí? ¿Es probable que esto haya sucedido?

Sí, ellos son correctos.

La insertparte de select intowas rolled back, pero no libera ningún espacio de datos. Puede verificar esto ejecutando sp_spaceused; verás mucho unallocated space.

Si desea que la base de datos libere este espacio no asignado, debe tener shrinksus archivos de datos.


15

Tienes razón, el SELECT...INTOcomando no es atómico. Esto no se documentó en el momento de la publicación original, pero ahora se menciona específicamente en la página de la cláusula SELECT - INTO (Transact-SQL) en MS Docs (¡yay código abierto!):

La SELECT...INTOinstrucción opera en dos partes: se crea la nueva tabla y luego se insertan las filas. Esto significa que si las inserciones fallan, todas se revertirán, pero la nueva tabla (vacía) permanecerá. Si necesita que toda la operación tenga éxito o falle como un todo, use una transacción explícita .

Crearé una base de datos que use el modelo de recuperación completa. Le daré un archivo de registro bastante pequeño y luego le diré que el archivo de registro no puede crecer automáticamente:

CREATE DATABASE [SelectIntoTestDB]
ON PRIMARY 
( 
    NAME = N'SelectIntoTestDB', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\SelectIntoTestDB.mdf', 
    SIZE = 8192KB, 
    FILEGROWTH = 65536KB
)
LOG ON 
( 
    NAME = N'SelectIntoTestDB_log', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\SelectIntoTestDB_log.ldf', 
    SIZE = 8192KB, 
    FILEGROWTH = 0
)

Y luego intentaré insertar todas las publicaciones de mi copia de la base de datos StackOverflow2010. Esto debería escribir un montón de cosas en el archivo de registro.

USE [SelectIntoTestDB];
GO

SELECT *
INTO dbo.Posts
FROM StackOverflow2010.dbo.Posts;

Esto dio como resultado el siguiente error después de ejecutarse durante 4 segundos:

Mensaje 9002, Nivel 17, Estado 4, Línea 1
El registro de transacciones para la base de datos 'SelectIntoTestDB' está lleno debido a 'ACTIVE_TRANSACTION'.

Pero hay una tabla de publicaciones vacía en mi nueva base de datos:

captura de pantalla de cero resultados de la tabla recién creada

Entonces, como sospechabas, lo CREATE TABLElograron, pero la INSERTporción fue revertida. Una solución alternativa sería utilizar una transacción explícita (que ya anotó en su pregunta).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.