Dlaczego nie otrzymuję minimalnego rejestrowania podczas wstawiania do indeksowanych tabel?

14

Testuję minimalne wstawki rejestrowania w różnych scenariuszach i z tego, co przeczytałem INSERT INTO SELECT w stercie z indeksem nieklastrowanym za pomocą TABLOCK i SQL Server 2016+ powinien się minimalnie logować, jednak w moim przypadku robię to pełne logowanie. Moja baza danych jest w prostym modelu odzyskiwania i udało mi się uzyskać minimalnie zalogowane wstawki na stercie bez indeksów i TABLOCK.

Korzystam ze starej kopii zapasowej bazy danych przepełnienia stosu, aby przetestować i utworzyłem replikę tabeli Posty z następującym schematem ...

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)

Następnie próbuję skopiować tabelę postów do tej tabeli ...

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

Patrząc na fn_dblog i wykorzystanie pliku dziennika, widzę, że nie otrzymuję z tego minimalnego logowania. Czytałem, że wersje przed 2016 wymagają flagi śledzenia 610, aby minimalnie logować się do indeksowanych tabel, próbowałem też to ustawić, ale nadal nie mam radości.

Chyba coś tu brakuje?

EDYCJA - Więcej informacji

Aby dodać więcej informacji, korzystam z następującej procedury, którą napisałem, aby wykryć minimalne rejestrowanie, być może coś tu jest nie tak ...

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

Wstawianie do sterty bez indeksów i TABLOCK przy użyciu następującego kodu ...

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

Dostaję te wyniki

wprowadź opis zdjęcia tutaj

Przy 0,0024mb wzrostu pliku dziennika, bardzo małych rozmiarów rekordu dziennika i bardzo niewielu z nich cieszę się, że używa to minimalnego rejestrowania.

Jeśli następnie utworzę indeks nieklastrowany na id ...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

Następnie uruchom ponownie tę samą wkładkę ...

wprowadź opis zdjęcia tutaj

Nie tylko nie otrzymuję minimalnego logowania do indeksu nieklastrowanego, ale także zgubiłem go na stercie. Po wykonaniu kilku testów wydaje się, że jeśli utworzę klastrowany identyfikator, to loguje się on minimalnie, ale z tego, co przeczytałem, 2016+ powinien minimalnie logować się do sterty z indeksem nieklastrowanym, gdy używany jest tablock.

EDYCJA KOŃCOWA :

Zgłosiłem to zachowanie do Microsoft na SQL Server UserVoice i zaktualizuję, jeśli otrzymam odpowiedź. Napisałem również pełne szczegóły dotyczące minimalnych scenariuszy dziennika, których nie mogłem uruchomić na https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/

Gavin
źródło
3
Paul White ma tutaj pomocną pomocną odpowiedź .
Erik Darling,

Odpowiedzi:

12

Mogę odtworzyć twoje wyniki na SQL Server 2017 przy użyciu bazy danych Stack Overflow 2010, ale nie (wszystkich) twoich wniosków.

Minimalne logowanie do sterty jest niedostępne, gdy jest używane INSERT...SELECTze TABLOCKstertą z indeksem nieklastrowanym, co jest nieoczekiwane . Domyślam się, że nie jest w INSERT...SELECTstanie obsłużyć ładunków masowych przy użyciu RowsetBulk(sterty) w tym samym czasie co FastLoadContext(b-drzewo). Tylko Microsoft byłby w stanie potwierdzić, czy jest to błąd, czy z założenia.

Nieklastrowany indeks na stercie jest minimalnie zalogowany (zakładając TF610 jest włączony, czy SQL Server 2016+ jest używany, umożliwiając FastLoadContext) z następującymi zastrzeżeniami:

  • Tylko wiersze wstawione do nowo przydzielonych stron są minimalnie rejestrowane.
  • Wiersze dodane do pierwszej strony indeksu nie są rejestrowane minimalnie, jeśli indeks był pusty na początku operacji.

497 LOP_INSERT_ROWSpozycji pokazanych dla indeksu nieklastrowanego odpowiada pierwszej stronie indeksu. Ponieważ indeks był wcześniej pusty, wiersze te są w pełni rejestrowane. Pozostałe wiersze są minimalnie rejestrowane . Jeśli udokumentowana flaga śledzenia 692 jest włączona (2016+), aby wyłączyć FastLoadContext, wszystkie nieklastrowane wiersze indeksu są minimalnie rejestrowane.


Okazało się, że minimalne rejestrowanie jest stosowana do obu hałdy i nieklastrowany gdy indeks ładowanego luzem tej samej tabeli (z indeksem), stosując BULK INSERTz pliku:

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

Zauważam to dla kompletności. Ładowanie zbiorcze przy INSERT...SELECTużyciu różnych ścieżek kodu, więc fakt, że zachowania się różnią, nie jest całkowicie nieoczekiwany.


Do pełnych informacji o logowaniu z użyciem minimalnej RowsetBulki FastLoadContextze INSERT...SELECTzobaczyć moje trzy serie części na SQLPerformance.com:

  1. Minimalne logowanie za pomocą INSERT… WYBIERZ do tabel stosów
  2. Minimalne logowanie za pomocą INSERT… WYBIERZ w pustych tabelach klastrowych
  3. Minimalne rejestrowanie za pomocą INSERT… SELECT i kontekst szybkiego ładowania

Inne scenariusze z Twojego posta na blogu

Komentarze są zamknięte, więc zajmę się nimi krótko tutaj.

Pusty indeks klastrowany z opcją śledzenia 610 lub 2016+

Jest to minimalnie rejestrowane przy użyciu FastLoadContextbez TABLOCK. Jedynie w pełni zarejestrowane wiersze są wstawiane na pierwszą stronę, ponieważ indeks klastrowany był pusty na początku transakcji.

Indeks klastrowy z danymi i danymi śledzenia 610 LUB 2016+

Jest to również minimalnie rejestrowane za pomocą FastLoadContext. Wiersze dodane do istniejącej strony są w pełni rejestrowane, pozostałe są rejestrowane minimalnie.

Indeks klastrowy z indeksami nieklastrowymi i TABLOCK lub Trace 610 / SQL 2016+

Można to również minimalnie rejestrować, FastLoadContextpod warunkiem, że indeks nieklastrowany jest obsługiwany przez osobnego operatora, DMLRequestSortustawiony na true, a pozostałe warunki określone w moich postach są spełnione.

Paul White 9
źródło
2

Poniższy dokument jest stary, ale nadal stanowi doskonałą lekturę.

W SQL 2016 flaga śledzenia 610 i ALLOW_PAGE_LOCKS są domyślnie włączone, ale ktoś mógł je wyłączyć.

Przewodnik po wydajności ładowania danych

(3) W zależności od planu wybranego przez optymalizator indeks nieklastrowany w tabeli może być w pełni lub minimalnie rejestrowany.

Instrukcja SELECT może być problemem, ponieważ masz TOP i ORDER BY. Wstawiasz dane do tabeli w innej kolejności niż indeks, więc SQL może dużo sortować w tle.

AKTUALIZACJA 2

Być może faktycznie otrzymujesz minimalne logowanie. Przy włączonym TraceFlag 610 dziennik zachowuje się inaczej, SQL zarezerwuje wystarczającą ilość miejsca w dzienniku, aby wykonać wycofanie, jeśli coś pójdzie nie tak, ale faktycznie nie będzie go używać.

Prawdopodobnie zlicza to Zarezerwowane (nieużywane) miejsce

EXEC('DBCC SQLPERF(logspace)')

Ten kod dzieli Zarezerwowane z Używane

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

Podejrzewam, że rejestrowanie minimalne (w przypadku Microsoft) w rzeczywistości polega na wykonywaniu najmniejszej liczby operacji we / wy w dzienniku, a nie na tym, ile dzienników jest zarezerwowanych.

Spójrz na ten link .

AKTUALIZACJA 1

Spróbuj użyć TABLOCKX zamiast TABLOCK. W Tablock nadal masz wspólną blokadę, więc SQL może się logować na wypadek, gdyby rozpoczął się inny proces.

TABLOCK może wymagać użycia w połączeniu z HOLDLOCK. Wymusza to Tablock do końca transakcji.

Zablokuj także tabelę źródłową [Posty], rejestrowanie może mieć miejsce, ponieważ tabela źródłowa może się zmienić w trakcie Twojej transakcji. Paul White osiągnął minimalne rejestrowanie, gdy źródłem nie była tabela SQL.

spokojnie
źródło