Mengapa saya tidak mendapatkan minimum logging saat memasukkan ke tabel yang diindeks

14

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

masukkan deskripsi gambar di sini

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 ...

masukkan deskripsi gambar di sini

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/

Gavin
sumber
3
Paul White memiliki jawaban terkait yang sangat membantu di sini .
Erik Darling

Jawaban:

12

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...SELECTdengan TABLOCKke heap dengan indeks nonclustered, yang tidak terduga . Dugaan saya adalah INSERT...SELECTtidak dapat mendukung beban massal menggunakan RowsetBulk(tumpukan) pada saat yang sama dengan FastLoadContext(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:

  • Hanya baris yang disisipkan ke halaman yang baru dialokasikan dicatat secara minimal.
  • Baris yang ditambahkan ke halaman indeks pertama tidak dicatat secara minimal, jika indeks kosong pada awal operasi.

497 LOP_INSERT_ROWSentri 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 dinonaktifkan FastLoadContext, 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 INSERTdari sebuah file:

BULK INSERT dbo.PostsDestination
FROM 'D:\SQL Server\Posts.bcp'
WITH (TABLOCK, DATAFILETYPE = 'native');

Saya perhatikan ini untuk kelengkapannya. Pemuatan massal menggunakan INSERT...SELECTjalur kode yang berbeda, sehingga fakta perilaku berbeda tidak sepenuhnya tak terduga.


Untuk detail lengkap tentang penebangan minimal menggunakan RowsetBulkdan FastLoadContextdengan INSERT...SELECTmelihat seri tiga bagian saya di SQLPerformance.com:

  1. Minimal Logging dengan INSERT… PILIH ke Heap Tables
  2. Minimal Logging dengan INSERT ... PILIH ke dalam Tabel Clustered Kosong
  3. Minimal Logging dengan INSERT ... SELECT dan Konteks Pemuatan Cepat

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 FastLoadContexttanpa menggunakan TABLOCK. 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 FastLoadContextselama indeks nonclustered dikelola oleh operator terpisah, DMLRequestSortdisetel ke true, dan kondisi lain yang diatur dalam posting saya terpenuhi.

Paul White 9
sumber
2

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

(3) Bergantung pada rencana yang dipilih oleh optimizer, indeks yang tidak tercakup pada tabel dapat dicatat secara penuh atau minimal.

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)

EXEC('DBCC SQLPERF(logspace)')

Kode ini memisahkan Reserved from Used

SELECT
    database_transaction_log_bytes_used
    ,database_transaction_log_bytes_reserved
    ,*
FROM sys.dm_tran_database_transactions 
WHERE database_id = DB_ID()

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.

pacreely
sumber