Pengantar
Mencapai pencatatan log minimal dengan INSERT...SELECT
bisa menjadi bisnis yang rumit. Pertimbangan yang tercantum dalam Panduan Kinerja Pemuatan Data masih cukup komprehensif, meskipun kita juga perlu membaca SQL Server 2016, Pencatatan minimal dan Dampak Batchsize dalam operasi pemuatan massal oleh Parikshit Savjani dari Tim SQL Server Tiger untuk mendapatkan gambaran yang diperbarui untuk SQL Server 2016 dan yang lebih baru, saat memuat massal ke dalam tabel rowstore berkerumun. Karena itu, artikel ini murni berkaitan dengan memberikan detail baru tentang pencatatan log minimal saat memuat secara massal tabel heap tradisional (bukan "memori-dioptimalkan") menggunakan INSERT...SELECT
. Tabel dengan indeks berkerumun b-tree dibahas secara terpisah di bagian dua seri ini.
Tabel tumpukan
Saat menyisipkan baris menggunakan INSERT...SELECT
ke dalam heap tanpa indeks nonclustered, dokumentasi secara universal menyatakan bahwa penyisipan tersebut akan dicatat secara minimal selama TABLOCK
petunjuk hadir. Hal ini tercermin dalam tabel ringkasan yang disertakan dalam Panduan Performa Pemuatan Data dan pos Tim Harimau. Baris ringkasan untuk tabel heap tanpa indeks sama di kedua dokumen (tidak ada perubahan untuk SQL Server 2016):
TABLOCK
yang eksplisit petunjuk bukan satu-satunya cara untuk memenuhi persyaratan penguncian tingkat tabel . Kami juga dapat mengatur 'kunci tabel pada beban massal' opsi untuk tabel target menggunakan sp_tableoption
atau dengan mengaktifkan bendera pelacakan terdokumentasi 715. (Catatan:Opsi ini tidak cukup untuk mengaktifkan logging minimal saat menggunakan INSERT...SELECT
karena INSERT...SELECT
tidak mendukung kunci pembaruan massal).
“mungkin bersamaan” kolom dalam ringkasan hanya berlaku untuk metode pemuatan massal selain INSERT...SELECT
. Pemuatan tabel heap secara bersamaan tidak mungkin dengan INSERT...SELECT
. Seperti yang tercantum dalam Panduan Kinerja Pemuatan Data , pemuatan massal dengan INSERT...SELECT
mengambil eksklusif X
kunci di atas meja, bukan pembaruan massal BU
kunci diperlukan untuk muatan massal bersamaan.
Selain semua itu — dan dengan asumsi tidak ada alasan lain untuk tidak mengharapkan logging minimal saat memuat secara massal tumpukan yang tidak diindeks dengan TABLOCK
(atau setara) — sisipan masih mungkin tidak login minimal…
Pengecualian terhadap Aturan
Skrip demo berikut harus dijalankan pada instance pengembangan di basis data pengujian baru setel untuk menggunakan SIMPLE
model pemulihan. Ini memuat sejumlah baris ke dalam tabel tumpukan menggunakan INSERT...SELECT
dengan TABLOCK
, dan laporan tentang catatan log transaksi yang dihasilkan:
CREATE TABLE dbo.TestHeap( id integer NOT NULL IDENTITY, c1 integer NOT NULL, padding char(45) NOT NULL DEFAULT '');GO-- Hapus logCHECKPOINT;GO-- Insert rowsINSERT dbo.TestHeap WITH (TABLOCK ) (c1)SELECT TOP (897) CHECKSUM(NEWID())FROM master.dbo.spt_values AS SV;GO-- Tampilkan entri logSELECT FD.Operation, FD.Context, FD.[Log Record Length], FD.[Log Cadangan], FD.AllocUnitName, FD.[Nama Transaksi], FD.[Informasi Kunci], FD.[Deskripsi]FROM sys.fn_dblog(NULL, NULL) AS FD;GO-- Hitung jumlah baris yang telah di-log sepenuhnyaSELECT [ Baris Tercatat Penuh] =COUNT_BIG(*) FROM sys.fn_dblog(NULL, NULL) AS FDWHERE FD.Operation =N'LOP_INSERT_ROWS' AND FD.Context =N'LCX_HEAP' AND FD.AllocUnitName =N'dbo.TestHeap';Keluarannya menunjukkan bahwa 897 baris sepenuhnya dicatat meskipun tampaknya memenuhi semua persyaratan untuk logging minimal (hanya contoh catatan log yang ditampilkan karena alasan ruang):
Hasil yang sama terlihat jika penyisipan diulang (yaitu tidak masalah jika tabel heap kosong atau tidak). Hasil ini bertentangan dengan dokumentasi.
Ambang Logging Minimal untuk Heap
Jumlah baris yang perlu ditambahkan dalam satu
INSERT...SELECT
pernyataan untuk mencapai logging minimal ke dalam tumpukan yang tidak diindeks dengan penguncian tabel diaktifkan bergantung pada perhitungan yang dilakukan SQL Server saat memperkirakan ukuran total dari data yang akan dimasukkan. Masukan untuk perhitungan ini adalah:
- Versi SQL Server.
- Perkiraan jumlah baris yang mengarah ke Sisipkan operator.
- Ukuran baris tabel target.
Untuk SQL Server 2012 dan sebelumnya , titik transisi untuk tabel khusus ini adalah 898 baris . Mengubah nomor dalam skrip demo TOP
klausa dari 897 hingga 898 menghasilkan output berikut:
Entri log transaksi yang dihasilkan berkaitan dengan alokasi halaman dan pemeliharaan Peta Alokasi Indeks (IAM) dan Ruang Kosong Halaman (PFS) struktur. Ingatlah bahwa pencatatan log minimal berarti SQL Server tidak mencatat setiap penyisipan baris satu per satu. Sebaliknya, hanya perubahan metadata dan struktur alokasi yang dicatat. Mengubah dari 897 menjadi 898 baris memungkinkan logging minimal untuk tabel khusus ini.
Untuk SQL Server 2014 dan yang lebih baru , titik transisinya adalah 950 baris untuk tabel ini. Menjalankan INSERT...SELECT
dengan TOP (949)
akan menggunakan pencatatan log penuh – mengubah ke TOP (950)
akan menghasilkan logging minimal .
Ambangnya tidak bergantung pada Estimasi Kardinalitas model yang digunakan atau tingkat kompatibilitas database.
Perhitungan Ukuran Data
Apakah SQL Server memutuskan untuk menggunakan load massal rowset — dan karena itu apakah pencatatan log minimal tersedia atau tidak — tergantung pada hasil dari serangkaian perhitungan yang dilakukan dalam metode yang disebut sqllang!CUpdUtil::FOptimizeInsert
, yang mengembalikan true untuk logging minimal, atau false untuk pencatatan penuh. Contoh tumpukan panggilan ditunjukkan di bawah ini:
Inti dari tes ini adalah:
- Sisipan harus untuk lebih dari 250 baris .
- Total ukuran data sisipan harus dihitung sebagai minimal 8 halaman .
Pemeriksaan untuk lebih dari 250 baris hanya bergantung pada perkiraan jumlah baris yang tiba di Sisipkan Tabel operator. Ini ditunjukkan dalam rencana eksekusi sebagai 'Perkiraan Jumlah Baris' . Hati-hati dengan ini. Sangat mudah untuk menghasilkan rencana dengan perkiraan jumlah baris yang rendah, misalnya dengan menggunakan variabel di TOP
klausa tanpa OPTION (RECOMPILE)
. Dalam hal ini, pengoptimal menebak 100 baris, yang tidak akan mencapai ambang batas, sehingga mencegah pemuatan massal dan pencatatan minimum.
Penghitungan ukuran data total lebih rumit, dan tidak cocok 'Perkiraan Ukuran Baris' mengalir ke Sisipkan Tabel operator. Cara penghitungan yang dilakukan sedikit berbeda di SQL Server 2012 dan sebelumnya dibandingkan dengan SQL Server 2014 dan yang lebih baru. Namun, keduanya menghasilkan hasil ukuran baris yang berbeda dari apa yang terlihat dalam rencana eksekusi.
Perhitungan Ukuran Baris
Total ukuran data sisipan dihitung dengan mengalikan perkiraan jumlah baris dengan ukuran baris maksimum yang diharapkan . Perhitungan ukuran baris adalah titik yang membedakan antara versi SQL Server.
Di SQL Server 2012 dan sebelumnya, penghitungan dilakukan oleh sqllang!OptimizerUtil::ComputeRowLength
. Untuk tabel heap pengujian (sengaja dirancang dengan kolom non-null panjang tetap sederhana menggunakan FixedVar asli format penyimpanan baris) garis besar perhitungannya adalah:
- Inisialisasi FixedVar pembuat metadata.
- Dapatkan informasi jenis dan atribut untuk setiap kolom di Sisipkan Tabel aliran masukan.
- Tambahkan kolom dan atribut yang diketik ke metadata.
- Selesaikan generator dan tanyakan ukuran baris maksimumnya.
- Tambahkan overhead untuk null bitmap dan jumlah kolom.
- Tambahkan empat byte untuk baris bit status dan offset baris ke jumlah kolom data.
Ukuran Baris Fisik
Hasil perhitungan ini mungkin diharapkan cocok dengan ukuran baris fisik, tetapi ternyata tidak. Misalnya, dengan menonaktifkan versi baris untuk database:
…memberikan ukuran rekor 60 byte di setiap baris tabel pengujian:
Ini seperti yang dijelaskan dalam Perkirakan Ukuran Tumpukan:
- Total ukuran byte dari semua panjang tetap kolom =53 byte:
id integer NOT NULL
=4 bytec1 integer NOT NULL
=4 bytepadding char(45) NOT NULL
=45 byte.
- Bitmap nol =3 byte :
- =2 + int((Num_Cols + 7) / 8)
- =2 + int((3 + 7) / 8)
- =3 byte.
- Tajuk baris =4 byte .
- Total 53 + 3 + 4 =60 byte .
Ini juga cocok dengan perkiraan ukuran baris yang ditampilkan dalam rencana eksekusi:
Rincian Perhitungan Internal
Perhitungan internal yang digunakan untuk menentukan apakah muatan massal digunakan memberikan hasil yang berbeda, berdasarkan masukkan aliran berikut informasi kolom yang diperoleh menggunakan debugger. Jenis nomor yang digunakan cocok dengan sys.types
:
- Total panjang tetap ukuran kolom =66 byte :
- Ketik id 173
binary(8)
=8 byte (internal). - Ketik id 56
integer
=4 byte (internal). - Ketik id 104
bit
=1 byte (internal). - Ketik id 56
integer
=4 byte (id
kolom). - Ketik id 56
integer
=4 byte (c1
kolom). - Ketik id 175
char(45)
=45 byte (padding
kolom).
- Ketik id 173
- Bitmap nol =3 byte (seperti sebelumnya).
- Tajuk baris overhead =4 byte (seperti sebelumnya).
- Ukuran baris yang dihitung =66 + 3 + 4 =73 byte .
Perbedaannya adalah bahwa aliran input mengumpankan Sisipkan Tabel operator berisi tiga kolom internal tambahan . Ini dilucuti ketika showplan dibuat. Kolom tambahan membentuk pencari lokasi penyisipan tabel , yang menyertakan bookmark (RID atau pencari baris) sebagai komponen pertamanya. Ini adalah metadata untuk sisipan dan tidak berakhir ditambahkan ke tabel.
Kolom tambahan menjelaskan perbedaan antara perhitungan yang dilakukan oleh OptimizerUtil::ComputeRowLength
dan ukuran fisik baris. Ini dapat dilihat sebagai bug :SQL Server tidak boleh menghitung kolom metadata dalam aliran penyisipan ke ukuran fisik akhir baris. Di sisi lain, penghitungan mungkin hanya merupakan perkiraan upaya terbaik menggunakan pembaruan generic generik operator.
Perhitungan juga tidak memperhitungkan faktor lain seperti overhead 14-byte dari versi baris. Ini dapat diuji dengan menjalankan kembali skrip demo dengan salah satu isolasi snapshot atau baca isolasi snapshot yang dilakukan opsi basis data diaktifkan. Ukuran fisik baris akan bertambah 14 byte (dari 60 byte menjadi 74), tetapi ambang batas untuk logging minimal tetap tidak berubah pada 898 baris.
Penghitungan Ambang Batas
Kami sekarang memiliki semua detail yang kami perlukan untuk melihat mengapa ambang batas adalah 898 baris untuk tabel ini di SQL Server 2012 dan sebelumnya:
- 898 baris memenuhi persyaratan pertama untuk lebih dari 250 baris .
- Ukuran baris yang dihitung =73 byte.
- Perkiraan jumlah baris =897.
- Total ukuran data =73 byte * 897 baris =65481 byte.
- Total halaman =65481 / 8192 =7.9932861328125.
- Ini hanya di bawah persyaratan kedua untuk>=8 halaman.
- Untuk 898 baris, jumlah halamannya adalah 8.002197265625.
- Ini >=8 halaman jadi login minimal diaktifkan.
Di SQL Server 2014 dan yang lebih baru , perubahannya adalah:
- Ukuran baris dihitung oleh generator metadata.
- Kolom bilangan bulat internal di pencari tabel tidak lagi ada di aliran sisipan. Ini mewakili pembeda , yang hanya berlaku untuk indeks. Sepertinya ini telah dihapus sebagai perbaikan bug.
- Ukuran baris yang diharapkan berubah dari 73 menjadi 69 byte karena kolom bilangan bulat yang dihilangkan (4 byte).
- Ukuran fisiknya masih 60 byte. Perbedaan 9 byte yang tersisa diperhitungkan oleh kolom internal RID 8-byte tambahan dan 1-byte bit dalam aliran penyisipan.
Untuk mencapai ambang batas 8 halaman dengan 69 byte per baris:
- 8 halaman * 8192 byte per halaman =65536 byte.
- 65535 byte / 69 byte per baris =949.7971014492754 baris.
- Oleh karena itu, kami mengharapkan minimal 950 baris untuk mengaktifkan pemuatan massal rowset untuk tabel ini di SQL Server 2014 dan seterusnya.
Ringkasan dan Pemikiran Akhir
Berbeda dengan metode pemuatan massal yang mendukung ukuran batch , seperti yang dicakup dalam pos oleh Parikesit Savjani, INSERT...SELECT
ke dalam tumpukan yang tidak diindeks (kosong atau tidak) tidak selalu menghasilkan logging minimal saat penguncian tabel ditentukan.
Untuk mengaktifkan logging minimal dengan INSERT...SELECT
, SQL Server harus mengharapkan lebih dari 250 baris dengan ukuran total setidaknya satu tingkat (8 halaman).
Saat menghitung perkiraan ukuran sisipan total (untuk membandingkan dengan ambang 8 halaman), SQL Server mengalikan perkiraan jumlah baris dengan ukuran baris maksimum yang dihitung. SQL Server menghitung kolom internal hadir dalam aliran sisipan saat menghitung ukuran baris. Untuk SQL Server 2012 dan yang lebih lama, ini menambahkan 13 byte per baris. Untuk SQL Server 2014 dan yang lebih baru, ia menambahkan 9 byte per baris. Ini hanya mempengaruhi perhitungan; itu tidak mempengaruhi ukuran fisik akhir baris.
Saat beban massal heap dengan log minimal aktif, SQL Server tidak menyisipkan baris satu per satu. Ekstensi dialokasikan terlebih dahulu, dan baris yang akan disisipkan dikumpulkan ke dalam halaman baru oleh sqlmin!RowsetBulk
sebelum ditambahkan ke struktur yang ada. Contoh tumpukan panggilan ditunjukkan di bawah ini:
Pembacaan logis tidak dilaporkan untuk tabel target saat digunakan beban massal heap dengan log minimal – Sisipkan Tabel operator tidak perlu membaca halaman yang ada untuk menemukan titik penyisipan untuk setiap baris baru.
Rencana eksekusi saat ini tidak ditampilkan berapa banyak baris atau halaman yang disisipkan menggunakan pemuatan massal rowset dan pencatatan log minimal . Mungkin informasi yang berguna ini akan ditambahkan ke produk di rilis mendatang.