Por que não estou obtendo log mínimo ao inserir em tabelas indexadas

14

Estou testando inserções mínimas de log em diferentes cenários e pelo que li INSERT INTO SELECT em um heap com um índice não clusterizado usando TABLOCK e SQL Server 2016+ deve registrar minimamente, no entanto, no meu caso, ao fazer isso, estou obtendo log completo. Meu banco de dados está no modelo de recuperação simples e recebo inserções minimamente registradas em um heap sem índices e TABLOCK.

Estou usando um backup antigo do banco de dados Stack Overflow para testar e criei uma replicação da tabela Posts com o seguinte esquema ...

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)

Em seguida, tento copiar a tabela de postagens para esta tabela ...

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

Observando o fn_dblog e o uso do arquivo de log, vejo que não estou obtendo um log mínimo disso. Eu li que as versões anteriores a 2016 exigem que o sinalizador de rastreamento 610 registre minimamente em tabelas indexadas, também tentei definir isso, mas ainda assim não há alegria.

Acho que estou perdendo alguma coisa aqui?

EDIT - Mais informações

Para adicionar mais informações, estou usando o procedimento a seguir que escrevi para tentar detectar o mínimo de log, talvez tenha algo errado aqui ...

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

Inserindo em um heap sem índices e TABLOCK usando o seguinte código ...

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

Eu recebo esses resultados

insira a descrição da imagem aqui

Com um crescimento de 0,0024mb de arquivo de log, tamanhos muito pequenos de registros de log e muito poucos deles, fico feliz que isso esteja usando o log mínimo.

Se eu criar um índice não clusterizado no id ...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

Em seguida, execute minha mesma inserção novamente ...

insira a descrição da imagem aqui

Não apenas não estou obtendo log mínimo no índice não clusterizado, como também o perdi no heap. Depois de fazer mais alguns testes, parece que, se eu criar um ID em cluster, ele registra minimamente, mas pelo que li 2016+, deve registrar minimamente em um heap com índice não agrupado quando o tablock é usado.

EDIÇÃO FINAL :

Eu relatei o comportamento à Microsoft no SQL Server UserVoice e atualizarei se eu receber uma resposta. Também escrevi todos os detalhes dos cenários mínimos de log em que não consegui trabalhar em https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/

Gavin
fonte
3
Paul White tem uma resposta relacionada útil aqui .
Erik Darling

Respostas:

12

Posso reproduzir seus resultados no SQL Server 2017 usando o banco de dados Stack Overflow 2010, mas não (todas) suas conclusões.

Log mínimo para a pilha não está disponível quando se utiliza INSERT...SELECTcom TABLOCKuma pilha com um índice agrupado, o que é inesperado . Meu palpite é INSERT...SELECTque não é possível suportar cargas em massa usando RowsetBulk(heap) ao mesmo tempo que FastLoadContext(b-tree). Somente a Microsoft poderia confirmar se isso é um bug ou por design.

O índice não clusterizado no heap é minimamente registrado (supondo que o TF610 esteja ativado ou o SQL Server 2016+ seja usado, habilitando FastLoadContext) com as seguintes advertências:

  • Somente as linhas inseridas nas páginas recém-alocadas são minimamente registradas.
  • As linhas adicionadas à primeira página de índice não são minimamente registradas, se o índice estava vazio no início da operação.

As 497 LOP_INSERT_ROWSentradas mostradas para o índice não clusterizado correspondem à primeira página do índice. Como o índice estava vazio anteriormente, essas linhas são totalmente registradas. As linhas restantes são todas minimamente registradas . Se o sinalizador de rastreamento documentado 692 estiver ativado (2016 ou mais) para desativar FastLoadContext, todas as linhas de índice não clusterizadas serão minimamente registradas.


Descobri que log mínimo é aplicado a ambos o montão e índice agrupado quando grandes quantidades de carregar a mesma tabela (com índice) usando BULK INSERTa partir de um arquivo:

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

Eu noto isso por completo. O carregamento em massa usando INSERT...SELECTusa caminhos de código diferentes, portanto, o fato de os comportamentos diferirem não é totalmente inesperado.


Para obter detalhes completos sobre o registro mínimo usando RowsetBulke FastLoadContextcom INSERT...SELECTconsulte minhas séries de três partes no SQLPerformance.com:

  1. Registro mínimo com INSERT… SELECT nas tabelas de heap
  2. Registro mínimo com INSERT… SELECT em tabelas em cluster vazias
  3. Registro mínimo com INSERT… SELECT e contexto de carregamento rápido

Outros cenários da sua postagem no blog

Os comentários estão fechados, por isso irei abordar aqui brevemente.

Índice de cluster vazio com rastreamento 610 ou 2016+

Isso é registrado minimamente usando FastLoadContextsem TABLOCK. As únicas linhas totalmente registradas são aquelas inseridas na primeira página porque o índice em cluster estava vazio no início da transação.

Índice agrupado com dados e rastreamento 610 OR 2016+

Isso também é minimamente registrado usando FastLoadContext. As linhas adicionadas à página existente são totalmente registradas e as demais são minimamente registradas.

Índice agrupado com índices não agrupados e TABLOCK ou rastreamento 610 / SQL 2016+

Isso também pode ser registrado minimamente FastLoadContext, desde que o índice não clusterizado seja mantido por um operador separado, DMLRequestSortdefinido como true e as outras condições estabelecidas em minhas postagens sejam atendidas.

Paul White 9
fonte
2

O documento abaixo é antigo, mas ainda é uma excelente leitura.

No sinalizador de rastreamento do SQL 2016 610 e ALLOW_PAGE_LOCKS estão ativados por padrão, mas alguém pode ter desativado.

Guia de desempenho de carregamento de dados

(3) Dependendo do plano escolhido pelo otimizador, o índice não clusterizado na tabela pode ser registrado total ou minimamente.

A instrução SELECT pode ser o problema porque você tem um TOP e ORDER BY. Você está inserindo dados na Tabela em uma ordem diferente do Índice, portanto, o SQL pode estar classificando bastante em segundo plano.

ATUALIZAÇÃO 2

Você pode realmente estar recebendo o log mínimo. Com o TraceFlag 610 ativado, o log se comporta de maneira diferente, o SQL reservará espaço suficiente no log para executar uma reversão se algo der errado, mas na verdade não usará o log.

Provavelmente está contando o espaço Reservado (não utilizado)

EXEC('DBCC SQLPERF(logspace)')

Este código divide reservado de usado

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

Suponho que o log mínimo (no que diz respeito à Microsoft) seja realmente sobre executar a menor quantidade de E / S no log, e não quanto do log está reservado.

Dê uma olhada neste link .

ATUALIZAÇÃO 1

Tente usar TABLOCKX em vez de TABLOCK. Com o Tablock, você ainda tem um bloqueio compartilhado; portanto, o SQL pode estar efetuando logon caso outro processo inicie.

O TABLOCK pode precisar ser usado em conjunto com o HOLDLOCK. Isso impõe o Tablock até o final da sua transação.

Coloque também um bloqueio na tabela de origem [Posts], o log pode estar ocorrendo porque a tabela de origem pode mudar enquanto a transação está ocorrendo. Paul White alcançou um log mínimo quando a fonte não era uma tabela SQL.

pacificamente
fonte