Saya sedang menguji sisipan logging minimal dalam skenario yang berbeda dan dari apa yang saya baca INSERT INTO SELECT menjadi tumpukan dengan indeks non clustered menggunakan TABLOCK dan SQL Server 2016+ harus minimal login, namun dalam kasus saya ketika melakukan ini saya mendapatkan penebangan penuh. Basis data saya berada dalam model pemulihan sederhana dan saya berhasil mendapatkan sisipan yang minimal dicatat di heap tanpa indeks dan TABLOCK.
Saya menggunakan cadangan lama dari database Stack Overflow untuk menguji dan telah membuat replikasi tabel Posts dengan skema berikut ...
CREATE TABLE [dbo].[PostsDestination](
[Id] [int] NOT NULL,
[AcceptedAnswerId] [int] NULL,
[AnswerCount] [int] NULL,
[Body] [nvarchar](max) NOT NULL,
[ClosedDate] [datetime] NULL,
[CommentCount] [int] NULL,
[CommunityOwnedDate] [datetime] NULL,
[CreationDate] [datetime] NOT NULL,
[FavoriteCount] [int] NULL,
[LastActivityDate] [datetime] NOT NULL,
[LastEditDate] [datetime] NULL,
[LastEditorDisplayName] [nvarchar](40) NULL,
[LastEditorUserId] [int] NULL,
[OwnerUserId] [int] NULL,
[ParentId] [int] NULL,
[PostTypeId] [int] NOT NULL,
[Score] [int] NOT NULL,
[Tags] [nvarchar](150) NULL,
[Title] [nvarchar](250) NULL,
[ViewCount] [int] NOT NULL
)
CREATE NONCLUSTERED INDEX ndx_PostsDestination_Id ON PostsDestination(Id)
Saya kemudian mencoba menyalin tabel posting ke tabel ini ...
INSERT INTO PostsDestination WITH(TABLOCK)
SELECT * FROM Posts ORDER BY Id
Dari melihat fn_dblog dan penggunaan file log saya bisa melihat saya tidak mendapatkan minimal logging dari ini. Saya telah membaca bahwa versi sebelum 2016 memerlukan jejak bendera 610 untuk masuk secara minimum ke tabel yang diindeks, saya juga telah mencoba mengatur ini tetapi masih tidak ada sukacita.
Saya kira saya kehilangan sesuatu di sini?
EDIT - Info Lebih Lanjut
Untuk menambahkan lebih banyak info, saya menggunakan prosedur berikut yang saya tulis untuk mencoba mendeteksi penebangan minimal, mungkin ada yang salah di sini ...
/*
Example Usage...
EXEC sp_GetLogUseStats
@Sql = '
INSERT INTO PostsDestination
SELECT TOP 500000 * FROM Posts ORDER BY Id ',
@Schema = 'dbo',
@Table = 'PostsDestination',
@ClearData = 1
*/
CREATE PROCEDURE [dbo].[sp_GetLogUseStats]
(
@Sql NVARCHAR(400),
@Schema NVARCHAR(20),
@Table NVARCHAR(200),
@ClearData BIT = 0
)
AS
IF @ClearData = 1
BEGIN
TRUNCATE TABLE PostsDestination
END
/*Checkpoint to clear log (Assuming Simple/Bulk Recovery Model*/
CHECKPOINT
/*Snapshot of logsize before query*/
CREATE TABLE #BeforeLogUsed(
[Db] NVARCHAR(100),
LogSize NVARCHAR(30),
Used NVARCHAR(50),
Status INT
)
INSERT INTO #BeforeLogUsed
EXEC('DBCC SQLPERF(logspace)')
/*Run Query*/
EXECUTE sp_executesql @SQL
/*Snapshot of logsize after query*/
CREATE TABLE #AfterLLogUsed(
[Db] NVARCHAR(100),
LogSize NVARCHAR(30),
Used NVARCHAR(50),
Status INT
)
INSERT INTO #AfterLLogUsed
EXEC('DBCC SQLPERF(logspace)')
/*Return before and after log size*/
SELECT
CAST(#AfterLLogUsed.Used AS DECIMAL(12,4)) - CAST(#BeforeLogUsed.Used AS DECIMAL(12,4)) AS LogSpaceUsersByInsert
FROM
#BeforeLogUsed
LEFT JOIN #AfterLLogUsed ON #AfterLLogUsed.Db = #BeforeLogUsed.Db
WHERE
#BeforeLogUsed.Db = DB_NAME()
/*Get list of affected indexes from insert query*/
SELECT
@Schema + '.' + so.name + '.' + si.name AS IndexName
INTO
#IndexNames
FROM
sys.indexes si
JOIN sys.objects so ON si.[object_id] = so.[object_id]
WHERE
si.name IS NOT NULL
AND so.name = @Table
/*Insert Record For Heap*/
INSERT INTO #IndexNames VALUES(@Schema + '.' + @Table)
/*Get log recrod sizes for heap and/or any indexes*/
SELECT
AllocUnitName,
[operation],
AVG([log record length]) AvgLogLength,
SUM([log record length]) TotalLogLength,
COUNT(*) Count
INTO #LogBreakdown
FROM
fn_dblog(null, null) fn
INNER JOIN #IndexNames ON #IndexNames.IndexName = allocunitname
GROUP BY
[Operation], AllocUnitName
ORDER BY AllocUnitName, operation
SELECT * FROM #LogBreakdown
SELECT AllocUnitName, SUM(TotalLogLength) TotalLogRecordLength
FROM #LogBreakdown
GROUP BY AllocUnitName
Memasukkan ke heap tanpa indeks dan TABLOCK menggunakan kode berikut ...
EXEC sp_GetLogUseStats
@Sql = '
INSERT INTO PostsDestination
SELECT * FROM Posts ORDER BY Id ',
@Schema = 'dbo',
@Table = 'PostsDestination',
@ClearData = 1
Saya mendapatkan hasil ini
Pada pertumbuhan file log 0,0024mb, ukuran catatan log sangat kecil dan sangat sedikit dari mereka saya senang bahwa ini menggunakan logging minimal.
Jika saya kemudian membuat indeks non clustered pada id ...
CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)
Kemudian jalankan kembali insert yang sama ...
Bukan saja saya tidak mendapatkan minimum logging pada indeks non clustered tapi saya juga kehilangan itu di heap. Setelah melakukan beberapa tes lagi tampaknya jika saya membuat ID clustered itu log minimal tetapi dari apa yang saya baca 2016+ harus minimal login ke heap dengan indeks non clustered ketika tablock digunakan.
EDIT AKHIR :
Saya telah melaporkan perilaku ke Microsoft pada SQL Server UserVoice dan akan memperbarui jika saya mendapat respons. Saya juga telah menulis detail lengkap dari skenario log minimal yang saya tidak bisa mulai bekerja di https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/
sumber
Jawaban:
Saya dapat mereproduksi hasil Anda di SQL Server 2017 menggunakan database Stack Overflow 2010, tetapi tidak (semua) kesimpulan Anda.
Pencatatan minimum ke heap tidak tersedia saat menggunakan
INSERT...SELECT
denganTABLOCK
ke heap dengan indeks nonclustered, yang tidak terduga . Dugaan saya adalahINSERT...SELECT
tidak dapat mendukung beban massal menggunakanRowsetBulk
(tumpukan) pada saat yang sama denganFastLoadContext
(b-tree). Hanya Microsoft yang dapat mengkonfirmasi apakah ini bug atau desain.The indeks nonclustered di heap adalah minimal login (dengan asumsi TF610 aktif, atau SQL Server 2016+ digunakan, memungkinkan
FastLoadContext
) dengan peringatan berikut:497
LOP_INSERT_ROWS
entri yang diperlihatkan untuk indeks yang tidak dikelompokkan sesuai dengan halaman pertama dari indeks. Karena indeks sebelumnya kosong, baris ini sepenuhnya dicatat. Baris yang tersisa semuanya minimal dicatat . Jika flag jejak 692 yang didokumentasikan diaktifkan (2016+) untuk dinonaktifkanFastLoadContext
, semua baris indeks yang tidak dikecualikan dicatat minimal.Saya menemukan bahwa minimal penebangan diterapkan untuk kedua tumpukan dan indeks nonclustered saat curah memuat tabel yang sama (dengan indeks) menggunakan
BULK INSERT
dari sebuah file:Saya perhatikan ini untuk kelengkapannya. Pemuatan massal menggunakan
INSERT...SELECT
jalur kode yang berbeda, sehingga fakta perilaku berbeda tidak sepenuhnya tak terduga.Untuk detail lengkap tentang penebangan minimal menggunakan
RowsetBulk
danFastLoadContext
denganINSERT...SELECT
melihat seri tiga bagian saya di SQLPerformance.com:Skenario lain dari posting blog Anda
Komentar ditutup sehingga saya akan membahasnya secara singkat di sini.
Indeks Clustered Kosong Dengan Jejak 610 Atau 2016+
Ini minimal dicatat
FastLoadContext
tanpa menggunakanTABLOCK
. Satu-satunya baris yang sepenuhnya dicatat adalah yang disisipkan ke halaman pertama karena indeks yang dikelompokkan kosong pada awal transaksi.Indeks Clustered Dengan Data dan Jejak 610 OR 2016+
Ini juga menggunakan log minimal
FastLoadContext
. Baris yang ditambahkan ke halaman yang ada dicatat secara penuh, sisanya disimpan secara minimal.Indeks Clustered Dengan Indeks NonClustered dan TABLOCK Atau Jejak 610 / SQL 2016+
Ini juga dapat dicatat secara minimal menggunakan
FastLoadContext
selama indeks nonclustered dikelola oleh operator terpisah,DMLRequestSort
disetel ke true, dan kondisi lain yang diatur dalam posting saya terpenuhi.sumber
Dokumen di bawah ini sudah tua tetapi masih merupakan bacaan yang sangat baik.
Di SQL 2016 jejak bendera 610 dan ALLOW_PAGE_LOCKS diaktifkan secara default, tetapi seseorang mungkin telah menonaktifkannya.
Panduan Kinerja Muat Data
Pernyataan SELECT mungkin menjadi masalah karena Anda memiliki TOP dan ORDER OLEH. Anda memasukkan data ke dalam Tabel dalam urutan yang berbeda dengan Indeks, jadi SQL mungkin melakukan banyak Penyortiran di latar belakang.
PEMBARUAN 2
Anda mungkin benar-benar mendapatkan Minimal logging. Dengan TraceFlag 610 ON, Log berperilaku berbeda, SQL akan mencadangkan cukup ruang di Log untuk melakukan Roll-back jika ada masalah, tetapi tidak akan benar-benar menggunakan Log.
Ini mungkin menghitung ruang Cadangan (tidak digunakan)
Kode ini memisahkan Reserved from Used
Saya kira Minimal logging (sejauh menyangkut Microsoft) sebenarnya tentang melakukan paling sedikit IO pada log, dan bukan berapa banyak log yang dipesan.
Lihatlah tautan ini .
PEMBARUAN 1
Coba gunakan TABLOCKX alih-alih TABLOCK. Dengan Tablock Anda masih memiliki kunci bersama, jadi SQL mungkin masuk jika proses lain dimulai.
TABLOCK mungkin perlu digunakan bersamaan dengan HOLDLOCK. Ini memberlakukan Tablock hingga akhir transaksi Anda.
Juga letakkan kunci di tabel sumber [Posting], logging mungkin terjadi karena tabel sumber bisa berubah saat transaksi Anda sedang berlangsung. Paul White mencapai penebangan minimal ketika sumbernya bukan tabel SQL.
sumber