Perché non ottengo una registrazione minima durante l'inserimento in tabelle indicizzate


14

Sto testando inserti di registrazione minimi in diversi scenari e da quello che ho letto INSERT INTO SELECT in un heap con un indice non cluster usando TABLOCK e SQL Server 2016+ dovrebbe registrare minimamente, tuttavia nel mio caso quando sto facendo questo sto ottenendo registrazione completa. Il mio database è nel semplice modello di recupero e ottengo con successo inserimenti minimamente registrati su un heap senza indici e TABLOCK.

Sto usando un vecchio backup del database Stack Overflow per testare e ho creato una replica della tabella Posts con il seguente schema ...

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)

Provo quindi a copiare la tabella dei messaggi in questa tabella ...

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

Osservando fn_dblog e l'utilizzo del file di registro, vedo che non ottengo una registrazione minima da questo. Ho letto che le versioni precedenti al 2016 richiedono il flag di traccia 610 per accedere minimamente alle tabelle indicizzate, ho anche provato a impostare questo, ma ancora senza gioia.

Immagino che mi manchi qualcosa qui?

MODIFICA - Ulteriori informazioni

Per aggiungere ulteriori informazioni sto usando la seguente procedura che ho scritto per provare a rilevare la registrazione minima, forse ho qualcosa di sbagliato qui ...

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

Inserimento in un heap senza indici e TABLOCK utilizzando il seguente codice ...

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

Ottengo questi risultati

inserisci qui la descrizione dell'immagine

Con una crescita di file di registro di 0.0024mb, dimensioni di record di registro molto ridotte e pochissime, sono felice che questo stia utilizzando una registrazione minima.

Se poi creo un indice non cluster su id ...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

Quindi esegui di nuovo il mio stesso inserto ...

inserisci qui la descrizione dell'immagine

Non solo non sto ottenendo una registrazione minima sull'indice non cluster, ma l'ho anche perso nell'heap. Dopo aver fatto qualche altro test, sembra che se creo un ID cluster, accede minimamente, ma da quello che ho letto 2016+ dovrebbe accedere minimamente a un heap con indice non cluster quando viene usato il tablock.

MODIFICA FINALE :

Ho segnalato il comportamento a Microsoft su UserVoice di SQL Server e aggiornerò se ricevo una risposta. Ho anche scritto i dettagli completi degli scenari di registro minimi che non sono riuscito a lavorare su https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/


3
Paul White ha una risposta utile utile qui .
Erik Darling,

Risposte:


12

Posso riprodurre i tuoi risultati su SQL Server 2017 utilizzando il database Stack Overflow 2010, ma non (tutte) le tue conclusioni.

Registrazione minima al cumulo non è disponibile quando si utilizza INSERT...SELECTcon TABLOCKad un cumulo con un indice non cluster, che è inaspettato . La mia ipotesi è che INSERT...SELECTnon è possibile supportare carichi di massa utilizzando RowsetBulk(heap) contemporaneamente a FastLoadContext(b-tree). Solo Microsoft sarebbe in grado di confermare se si tratta di un bug o in base alla progettazione.

L' indice non cluster sull'heap è minimamente collegato (supponendo TF610 è acceso, o SQL Server 2016+ viene utilizzato, consentendo FastLoadContext) con le seguenti avvertenze:

  • Solo le righe inserite nelle pagine appena allocate vengono minimamente registrate.
  • Le righe aggiunte alla prima pagina dell'indice non vengono minimamente registrate, se l'indice era vuoto all'inizio dell'operazione.

Le 497 LOP_INSERT_ROWSvoci visualizzate per l'indice non cluster corrispondono alla prima pagina dell'indice. Poiché l'indice era precedentemente vuoto, queste righe sono completamente registrate. Le righe rimanenti sono tutte registrate minimamente . Se il flag di traccia documentato 692 è abilitato (2016+) per disabilitare FastLoadContext, tutte le righe dell'indice non cluster vengono registrate minimamente.


Ho trovato che la registrazione minima è applicata a sia mucchio e cluster indice quando rinfusa caricamento della stessa tabella (con indice) utilizzando BULK INSERTda un file:

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

Lo noto per completezza. Il caricamento collettivo tramite INSERT...SELECTutilizza percorsi di codice diversi, quindi il fatto che i comportamenti differiscano non è del tutto inaspettato.


Per i dettagli completi sulla registrazione minima utilizzando RowsetBulke FastLoadContextcon INSERT...SELECTvedere le mie serie in tre parti su SQLPerformance.com:

  1. Registrazione minima con INSERT ... SELECT in Heap Tables
  2. Registrazione minima con INSERT ... SELEZIONA in tabelle cluster vuote
  3. Registrazione minima con INSERT ... SELECT e contesto di caricamento rapido

Altri scenari dal tuo post sul blog

I commenti sono chiusi, quindi li affronterò brevemente qui.

Indice cluster vuoto con traccia 610 o 2016+

Questo è minimamente registrato usando FastLoadContextsenza TABLOCK. Le uniche righe completamente registrate sono quelle inserite nella prima pagina perché l'indice cluster era vuoto all'inizio della transazione.

Indice cluster con dati e traccia 610 o 2016+

Anche questo viene minimamente registrato usando FastLoadContext. Le righe aggiunte alla pagina esistente sono completamente registrate, il resto è minimamente registrato.

Indice cluster con indici non cluster e TABLOCK Or Trace 610 / SQL 2016+

Questo può anche essere minimamente registrato usando FastLoadContextfintanto che l'indice non cluster è gestito da un operatore separato, DMLRequestSortè impostato su true e le altre condizioni indicate nei miei post sono soddisfatte.


2

Il documento di seguito è vecchio ma è comunque una lettura eccellente.

In SQL 2016 il flag di traccia 610 e ALLOW_PAGE_LOCKS sono attivi per impostazione predefinita, ma qualcuno potrebbe averli disabilitati.

Guida alle prestazioni del caricamento dei dati

(3) A seconda del piano scelto dall'ottimizzatore, l'indice non cluster sulla tabella può essere registrato in modo completo o minimale.

L'istruzione SELECT potrebbe essere il problema perché hai un TOP e ORDER BY. Stai inserendo i dati nella tabella in un ordine diverso dall'indice, quindi SQL potrebbe fare molto ordinamento in background.

AGGIORNAMENTO 2

Potresti effettivamente ottenere una registrazione minima. Con TraceFlag 610 ON, il registro si comporta in modo diverso, SQL riserverà abbastanza spazio nel registro per eseguire un roll-back in caso di problemi, ma non utilizzerà effettivamente il registro.

Questo probabilmente sta contando lo spazio riservato (non utilizzato)

EXEC('DBCC SQLPERF(logspace)')

Questo codice si divide Riservato dall'usato

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

Suppongo che la registrazione minima (per quanto riguarda Microsoft) riguardi effettivamente l'esecuzione della minima quantità di I / O sul registro e non la quantità di registro riservata.

Dai un'occhiata a questo link .

AGGIORNAMENTO 1

Prova a utilizzare TABLOCKX anziché TABLOCK. Con Tablock hai ancora un blocco condiviso, quindi SQL potrebbe registrarsi nel caso in cui un altro processo inizi.

Potrebbe essere necessario utilizzare TABLOCK insieme a HOLDLOCK. Ciò impone Tablock fino alla fine della transazione.

Metti anche un lucchetto sulla tabella di origine [Posts], la registrazione potrebbe essere in atto perché la tabella di origine potrebbe cambiare mentre la transazione è in corso. Paul White ha ottenuto una registrazione minima quando l'origine non era una tabella SQL.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.