Dizinlenmiş tablolara eklerken neden minimum günlük kaydı alamıyorum?


14

Farklı senaryolarda en az günlük ekleri test ediyorum ve TABLOCK ve SQL Server 2016 + kullanarak kümelenmemiş bir dizin ile bir yığın içine INSERT INTOT okuduğumdan en az günlüğe kaydetmelidir, ancak bu durumda bunu yaparken tam günlük kaydı. Veritabanım basit kurtarma modelindedir ve dizinler ve TABLOCK içermeyen bir yığın üzerinde minimal olarak günlüğe eklenen ekler elde ederim.

Test etmek için yığın taşması veritabanının eski bir yedek kullanıyorum ve aşağıdaki şema ile Gönderiler tablosunun bir kopyası oluşturduk ...

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)

Sonra yazı tablosunu bu tabloya kopyalamaya çalışıyorum ...

INSERT INTO PostsDestination WITH(TABLOCK)
SELECT * FROM Posts ORDER BY Id 

Fn_dblog ve günlük dosyası kullanımına baktığımda bundan en az günlük kaydı alamadım. 2016'dan önceki sürümlerin dizinli tablolara minimal olarak giriş yapmak için izleme bayrağı 610 gerektirdiğini okudum, bunu da ayarlamayı denedim ama yine de hiç neşe yok.

Sanırım burada bir şey eksik mi?

DÜZENLE - Daha Fazla Bilgi

Daha fazla bilgi eklemek için, en az günlük kaydı algılamaya çalışmak üzere yazdığım aşağıdaki yordamı kullanıyorum, belki burada yanlış bir şey var ...

/*
    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

Aşağıdaki kodu kullanarak dizin ve TABLOCK içermeyen bir yığına ekleme ...

EXEC sp_GetLogUseStats
   @Sql = '
      INSERT INTO PostsDestination
      SELECT * FROM Posts ORDER BY Id ',
   @Schema = 'dbo',
   @Table = 'PostsDestination',
   @ClearData = 1

Bu sonuçları alıyorum

resim açıklamasını buraya girin

0.0024mb günlük dosyası büyüme, çok küçük günlük kayıt boyutları ve bunların çok azı, bu minimum günlük kaydı kullanmak mutluyum.

Sonra id üzerinde kümelenmemiş bir dizin oluşturmak ...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

Sonra aynı ek parçamı tekrar çalıştır ...

resim açıklamasını buraya girin

Sadece kümelenmemiş dizinde en az günlük kaydı almak değil, aynı zamanda yığın üzerinde kaybettim. Biraz daha fazla test yaptıktan sonra, kimlik kümelenmiş olsaydı en az günlüğe yazıyor ancak 2016 + 'dan okuduğumdan tablock kullanıldığında kümelenmemiş dizinle bir yığın için minimal olarak oturum açmalıdır.

NİHAİ DÜZENLEME :

Davranışı SQL Server UserVoice üzerinde Microsoft'a bildirdim ve bir yanıt alırsam güncelleştirilir. Ayrıca https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/ adresinde çalışamayacağım minimal günlük senaryolarının tüm ayrıntılarını yazdım.


Yanıtlar:


12

Sonuçlarınızı Stack Overflow 2010 veritabanını kullanarak SQL Server 2017'de çoğaltabilirim, ancak sonuçlarınızı (hepsi) değil.

Minimal günlüğü için yığın kullanırken kullanılamaz INSERT...SELECTile TABLOCKbir kümelenmemiş endeksi, bir yığın için beklenmedik . Benim tahminim (b) ağacı ile aynı anda (yığın) INSERT...SELECTkullanarak toplu yükleri destekleyemiyor . Bunun bir hata mı yoksa tasarım mı olduğunu sadece Microsoft doğrulayabilir.RowsetBulkFastLoadContext

Kümelenmemiş dizin öbek üzerinde minimal kaydedilir (TF610 üzerinde olduğunu varsayarak, veya SQL Server 2016+ sağlayan kullanılır FastLoadContextaşağıdaki uyarılar ile):

  • Yalnızca yeni atanan sayfalara eklenen satırlar en az günlüğe kaydedilir.
  • İşlemin başında dizin boşsa, ilk dizin sayfasına eklenen satırlar en az günlüğe kaydedilmez.

LOP_INSERT_ROWSKümelenmemiş dizin için gösterilen 497 giriş, dizinin ilk sayfasına karşılık gelir. Dizin önceden boş olduğundan, bu satırlar tamamen günlüğe kaydedilir. Kalan satırların tümü minimum düzeyde kaydedilir . Belgelenmiş izleme bayrağı 692 devre dışı bırakmak için etkinleştirilirse (2016+) FastLoadContext, kümelenmemiş tüm dizin satırları minimum düzeyde kaydedilir.


Minimal günlük tespit uygulandığı için , her iki kullanılarak yığın ve dökme (endeksi ile) aynı tablosu yükleme kümelenmemiş dizin BULK INSERTbir dosyadan:

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

Bunu bütünlük için not ediyorum. Kullanarak toplu yükleme INSERT...SELECTfarklı kod yolları kullanır, bu nedenle davranışların farklı olması tamamen beklenmedik değildir.


SQLPerformance.com'da kullanarak ve ile minimum günlük kaydı hakkında tüm ayrıntılar için üç bölüm serime bakın:RowsetBulkFastLoadContextINSERT...SELECT

  1. INSERT ile Minimal Logging… Yığın Tablolarına SELECT
  2. INSERT ile Minimum Günlük Kaydı… SELECT Boş Kümelenmiş Tablolara
  3. INSERT… SELECT ve Hızlı Yük Bağlamı ile Minimum Günlük Kaydı

Blog yayınınızdaki diğer senaryolar

Yorumlar kapalı, bu yüzden burada kısaca ele alacağım.

İzleme 610 veya 2016+ ile Boş Kümelenmiş Dizin

Bu, FastLoadContextolmadan kullanılarak minimal olarak kaydedilir TABLOCK. Tamamen günlüğe kaydedilen tek satırlar, kümelenmiş dizin işlemin başında boş olduğundan ilk sayfaya eklenen satırlardır.

Veri ve İzleme ile Kümelenmiş Dizin 610 VEYA 2016+

Bu ayrıca kullanılarak minimal olarak günlüğe kaydedilir FastLoadContext. Mevcut sayfaya eklenen satırlar tamamen günlüğe kaydedilir, geri kalanlar minimal olarak günlüğe kaydedilir.

Kümelenmemiş Dizinler ve TABLOCK Veya Trace 610 / SQL 2016+ ile Kümelenmiş Dizin

Bu aynı zamanda minimal kullanarak açmış olabilir FastLoadContextsürece kümelenmemiş dizin ayrı operatör tarafından korunduğu sürece DMLRequestSorttrue olarak ayarlanır ve yer alan diğer koşullar Yayınlarımda karşılanmaktadır.


2

Aşağıdaki belge eski ama yine de mükemmel bir okuma.

SQL 2016'da izleme bayrağı 610 ve ALLOW_PAGE_LOCKS varsayılan olarak açıktır, ancak birisi bunları devre dışı bırakmış olabilir.

Veri Yükü Performans Kılavuzu

(3) Optimize edici tarafından seçilen plana bağlı olarak, tablodaki kümelenmemiş dizin tamamen veya minimal olarak kaydedilebilir.

Bir SELECT ve ORDER BY aldığınız için SELECT Deyimi sorun olabilir. Tabloya dizin için farklı bir sırayla veri ekliyorsunuz, bu nedenle SQL arka planda çok fazla sıralama yapıyor olabilir.

GÜNCELLEME 2

Asgari günlük kaydı alıyor olabilirsiniz. TraceFlag 610 ON ile, Log farklı davranır, SQL işler yanlış giderse Geri Alma gerçekleştirmek için Log'da yeterli alan ayıracak, ancak gerçekten Log kullanmayacaktır.

Bu muhtemelen Ayrılmış (kullanılmayan) alanı sayar

EXEC('DBCC SQLPERF(logspace)')

Bu kod, Ayrılmış Kullanılandan ayrılır

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

En az günlüğe kaydetme (Microsoft söz konusu olduğunda), günlükte en az miktarda GÇ gerçekleştirme ile ilgilidir ve günlüğün ne kadarının ayrıldığını düşünmüyorum.

Bu bağlantıya bir göz atın .

GÜNCELLEME 1

TABLOCK yerine TABLOCKX kullanmayı deneyin. Tablock ile hala paylaşılan bir kilit var, bu nedenle başka bir işlemin başlaması durumunda SQL günlüğe kaydediliyor olabilir.

TABLOCK'un HOLDLOCK ile birlikte kullanılması gerekebilir. Bu işleminizin sonuna kadar Tablock'u zorlar.

Ayrıca kaynak tabloya bir kilit koyun [Mesajlar], işlem yapılırken kaynak tablo değişebileceğinden günlük kaydı olabilir. Paul White, kaynak bir SQL tablosu olmadığında minimum günlük kaydı elde etti.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.