Ruang disk penuh selama memasukkan, apa yang terjadi?

17

Hari ini saya menemukan harddisk yang menyimpan basis data saya penuh. Ini pernah terjadi sebelumnya, biasanya penyebabnya cukup jelas. Biasanya ada permintaan yang buruk, yang menyebabkan tumpahan besar untuk tempdb yang tumbuh hingga disk penuh. Kali ini agak kurang jelas apa yang terjadi, karena tempdb bukan penyebab drive penuh, itu adalah database itu sendiri.

Fakta:

  • Ukuran basis data yang biasa sekitar 55 GB, itu tumbuh menjadi 605 GB.
  • File log memiliki ukuran normal, datafile sangat besar.
  • Datafile memiliki 85% ruang yang tersedia (saya menafsirkan ini sebagai 'udara': ruang yang digunakan, tetapi telah dibebaskan. SQL Server cadangan semua ruang yang pernah dialokasikan).
  • Ukuran Tempdb normal.

Saya telah menemukan kemungkinan penyebabnya; ada satu kueri yang memilih terlalu banyak baris (bergabung dengan buruk menyebabkan pemilihan 11 miliar baris di mana beberapa ratus ribu diharapkan). Ini adalah SELECT INTOpermintaan, yang membuat saya bertanya-tanya apakah skenario berikut bisa terjadi:

  • SELECT INTO dieksekusi
  • Tabel target dibuat
  • Data dimasukkan saat dipilih
  • Disk terisi, menyebabkan insert gagal
  • SELECT INTO dibatalkan dan dibatalkan
  • Rollback membebaskan ruang (data yang sudah dimasukkan dihapus), tetapi SQL Server tidak merilis ruang yang dibebaskan.

Dalam situasi ini, bagaimanapun, saya tidak akan mengharapkan tabel yang dibuat oleh SELECT INTOmasih ada, itu harus dijatuhkan oleh rollback. Saya menguji ini:

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

ROLLBACK

SELECT  * 
FROM    TMP.test

Ini menghasilkan:

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

Namun tabel target memang ada. Namun, kueri aktual tidak dieksekusi dalam transaksi eksplisit, dapatkah itu menjelaskan keberadaan tabel target?

Apakah asumsi yang saya buat di sini benar? Apakah ini skenario yang mungkin terjadi?

Musang madu
sumber

Jawaban:

17

Namun, kueri aktual tidak dieksekusi dalam transaksi eksplisit, dapatkah itu menjelaskan keberadaan tabel target?

Ya persis seperti itu.

Jika Anda melakukan bagian select intoluar sederhana dari explicit transaction, ada dua transactionsdalam mode autocommit: yang pertama membuat tabledan yang kedua mengisinya.

Anda bisa membuktikannya kepada diri sendiri seperti ini:

Dalam didedikasikan databasepada server pengujian masuk simple recovery model, pertama-tama buat checkpointdan pastikan bahwa log hanya berisi beberapa baris (3 untuk 2016) yang terkait checkpoint. Kemudian jalankan select intosatu baris dan periksa loglagi, cari yang begin tranterkait dengan 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';

Anda akan mendapatkan 2 baris, menunjukkan Anda memiliki 2 transactions.

Apakah asumsi yang saya buat di sini benar? Apakah ini skenario yang mungkin terjadi?

Ya, mereka benar.

Bagian insertdari select intoitu rolled back, tetapi tidak merilis ruang data apa pun. Anda dapat memverifikasi ini dengan menjalankan sp_spaceused; Anda akan melihat banyak unallocated space.

Jika Anda ingin database merilis ruang yang tidak terisi ini, Anda harus menyimpan shrinkfile data Anda.

sepupik
sumber
15

Anda benar, SELECT...INTOperintahnya bukan atom. Ini tidak didokumentasikan pada saat posting asli, tetapi sekarang dipanggil secara khusus pada halaman SELECT - INTO Clause (Transact-SQL) di MS Documents (yay open source!):

The SELECT...INTOpernyataan beroperasi dalam dua bagian - tabel baru dibuat, dan baris kemudian dimasukkan. Ini berarti bahwa jika sisipan gagal, mereka semua akan digulung kembali, tetapi tabel baru (kosong) akan tetap ada. Jika Anda membutuhkan seluruh operasi untuk berhasil atau gagal secara keseluruhan, gunakan transaksi eksplisit .

Saya akan membuat database yang menggunakan model pemulihan penuh. Saya akan memberikan file log yang cukup kecil, dan kemudian memberi tahu bahwa file log tidak dapat autogrow:

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
)

Dan kemudian saya akan mencoba untuk memasukkan semua posting dari salinan database StackOverflow2010 saya. Ini harus menulis banyak hal ke file log.

USE [SelectIntoTestDB];
GO

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

Ini menghasilkan kesalahan berikut setelah berjalan selama 4 detik:

Msg 9002, Level 17, Negara 4, Baris 1
Log transaksi untuk database 'SelectIntoTestDB' penuh karena 'ACTIVE_TRANSACTION'.

Tetapi ada tabel Posting kosong di basis data baru saya:

tangkapan layar hasil nol dari tabel yang baru dibuat

Jadi, seperti yang Anda duga, itu CREATE TABLEberhasil, tetapi INSERTbagian itu semua dibatalkan. Solusinya adalah menggunakan transaksi eksplisit (yang sudah Anda catat dalam pertanyaan Anda).

Josh Darnell
sumber