인덱싱 된 테이블에 삽입 할 때 최소한의 로깅이 발생하지 않는 이유


14

다른 시나리오에서 최소 로깅 삽입을 테스트하고 있으며 TABLOCK을 사용하여 클러스터되지 않은 인덱스가있는 힙에 INSERT INTO SELECT를 읽은 것을 SQL Server 2016 +가 최소한으로 기록해야하지만 내 경우에는이 작업을 수행 할 때 전체 로깅. 내 데이터베이스는 단순 복구 모델에 있으며 인덱스와 TABLOCK이없는 힙에 최소한으로 기록 된 삽입을 얻습니다.

테스트하기 위해 Stack Overflow 데이터베이스의 오래된 백업을 사용하고 있으며 다음 스키마를 사용하여 Posts 테이블의 복제본을 만들었습니다.

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)

그런 다음 posts 테이블을이 테이블에 복사하려고합니다 ...

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

fn_dblog와 로그 파일 사용법을 보면 최소한의 로깅을 얻지 못한다는 것을 알 수 있습니다. 2016 이전의 버전에서는 인덱스 테이블에 최소한의 로그를 기록하기 위해 추적 플래그 610이 필요하다는 것을 읽었습니다.이 설정도 시도했지만 여전히 기쁨은 없습니다.

여기에 뭔가 빠진 것 같아요?

편집-추가 정보

더 많은 정보를 추가하기 위해 최소 로깅을 감지하기 위해 작성한 다음 절차를 사용하고 있습니다. 아마 여기에 잘못된 것이 있습니다 ...

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

다음 코드를 사용하여 색인과 TABLOCK이없는 힙에 삽입하는 중 ...

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

나는이 결과를 얻는다

여기에 이미지 설명을 입력하십시오

0.0024mb의 로그 파일 증가, 매우 작은 로그 레코드 크기 및 그 중 소수는 이것이 최소한의 로깅을 사용하고 있다는 사실에 만족합니다.

그런 다음 id에 클러스터되지 않은 인덱스를 만들면 ...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

그런 다음 동일한 삽입을 다시 실행하십시오 ...

여기에 이미지 설명을 입력하십시오

비 클러스터형 인덱스에서 최소 로깅을 얻지 못했을뿐만 아니라 힙에서도 잃어 버렸습니다. 몇 가지 테스트를 더한 후에 ID 클러스터를 만들면 최소한의 로그는 수행하지만 2016 +를 읽은 것부터 tablock을 사용할 때 클러스터되지 않은 인덱스가있는 힙에 최소한의 로그를 기록해야합니다.

최종 편집 :

SQL Server UserVoice 에서 Microsoft에 동작을보고했으며 응답을 받으면 업데이트됩니다. 또한 https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/ 에서 작동 할 수 없었던 최소 로그 시나리오에 대한 자세한 내용을 작성했습니다.


답변:


12

Stack Overflow 2010 데이터베이스를 사용하여 SQL Server 2017에서 결과를 재현 할 수 있지만 모든 결론은 아닙니다.

최소 로깅 사용하는 경우 사용할 수 없습니다 INSERT...SELECTTABLOCK있는 클러스터되지 않은 인덱스와 힙에 예기치 . 내 생각에 (b-tree) 와 동시에 (heap)을 INSERT...SELECT사용하는 벌크로드를 지원할 수 없습니다 . 이것이 버그인지 또는 의도적으로 Microsoft인지 확인할 수 있습니다.RowsetBulkFastLoadContext

클러스터되지 않은 인덱스 힙이 최소한으로 기록됩니다 (TF610은에 가정, 또는 SQL Server 2016+는 가능, 사용되는 FastLoadContext다음과 같은주의 사항과 함께) :

  • 새로 할당 된 페이지에 삽입 된 행만 최소한으로 기록됩니다.
  • 작업 시작시 인덱스가 비어 있으면 첫 번째 인덱스 페이지에 추가 된 행이 최소로 기록되지 않습니다.

LOP_INSERT_ROWS비 클러스터형 인덱스에 대해 표시된 497 개의 항목은 인덱스의 첫 페이지에 해당합니다. 인덱스가 미리 비어 있었으므로이 행은 완전히 기록됩니다. 나머지 행은 모두 최소한으로 기록 됩니다. 문서화 된 추적 플래그 692가 사용 FastLoadContext되지 않도록 설정 (2016+)되면 클러스터되지 않은 모든 인덱스 행이 최소로 기록됩니다.


나는 최소한의 로깅이 발견 적용모두 사용하여 힙 및 벌크 (인덱스) 같은 테이블을로드 클러스터되지 않은 인덱스 BULK INSERT파일에서 :

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

나는 이것을 완전성에 주목한다. 를 사용 INSERT...SELECT하는 대량로드 는 다른 코드 경로 를 사용하므로 동작이 다른 사실이 전혀 예상치 못한 것은 아닙니다.


에 대한 자세한 내용 사용하여 최소한의 로깅에 대한 RowsetBulkFastLoadContextINSERT...SELECTSQLPerformance.com 내 세 부분 시리즈를 참조하십시오

  1. 힙 테이블에 INSERT… SELECT를 사용하여 최소 로깅
  2. 빈 클러스터 테이블에 INSERT… SELECT를 사용하여 최소 로깅
  3. INSERT… SELECT 및 빠른로드 컨텍스트를 사용한 최소 로깅

블로그 게시물의 다른 시나리오

의견이 마감되었으므로 여기서 간단히 설명하겠습니다.

추적 610 또는 2016+가있는 빈 클러스터형 인덱스

이것은 FastLoadContextwithout없이 사용하여 최소한으로 기록됩니다 TABLOCK. 클러스터 된 인덱스가 트랜잭션을 시작할 때 비어 있기 때문에 완전히 기록 된 행은 첫 번째 페이지에 삽입 된 행뿐입니다.

데이터 및 추적이 포함 된 클러스터형 인덱스 610 또는 2016+

또한을 사용하여 최소한으로 기록됩니다 FastLoadContext. 기존 페이지에 추가 된 행은 완전히 기록되고 나머지는 최소한으로 기록됩니다.

비 클러스터형 인덱스 및 TABLOCK 또는 Trace 610 / SQL 2016+을 사용하는 클러스터형 인덱스

FastLoadContext비 클러스터형 인덱스가 별도의 연산자에 의해 유지 관리되고 DMLRequestSorttrue로 설정되고 내 게시물에 제시된 다른 조건 이 충족 되는 한이를 사용하여 최소한으로 기록 할 수도 있습니다 .


2

아래 문서는 오래되었지만 여전히 훌륭합니다.

SQL 2016에서 추적 플래그 610 및 ALLOW_PAGE_LOCKS가 기본적으로 켜져 있지만 누군가가이를 비활성화했을 수 있습니다.

데이터로드 성능 안내서

(3) 옵티마이 저가 선택한 계획에 따라 테이블의 비 클러스터형 인덱스가 전체 또는 최소로 기록 될 수 있습니다.

TOP 및 ORDER BY가있어 SELECT 문이 문제 일 수 있습니다. 인덱스와 다른 순서로 테이블에 데이터를 삽입하므로 SQL이 백그라운드에서 많은 정렬을 수행 할 수 있습니다.

업데이트 2

실제로 최소 로깅을 받고있을 수 있습니다. TraceFlag 610 ON을 사용하면 로그가 다르게 작동하므로 SQL은 로그에 충분한 공간을 확보하여 문제가 발생할 경우 롤백을 수행하지만 실제로는 로그를 사용하지 않습니다.

이것은 아마도 예약 된 공간 (사용하지 않은 공간)을 세는 것입니다.

EXEC('DBCC SQLPERF(logspace)')

이 코드는 사용에서 예약됨을 분리합니다.

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

최소 로깅 (Microsoft와 관련하여)은 실제로 로그에서 가장 적은 양의 IO를 수행하는 것이 아니라 예약 된 로그의 양이 아니라고 가정합니다.

링크를 살펴보십시오 .

업데이트 1

TABLOCK 대신 TABLOCKX를 사용해보십시오. Tablock을 사용하면 여전히 공유 잠금이 있으므로 다른 프로세스가 시작될 경우 SQL이 로깅 될 수 있습니다.

TABLOCK은 HOLDLOCK과 함께 사용해야합니다. 이렇게하면 거래가 끝날 때까지 Tablock이 시행됩니다.

또한 소스 테이블 [Posts]에 잠금을 설정하면 트랜잭션이 발생하는 동안 소스 테이블이 변경 될 수 있으므로 로깅이 수행 될 수 있습니다. Paul White는 소스가 SQL 테이블이 아닌 경우 최소한의 로깅을 달성했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.