Tại sao tôi không nhận được ghi nhật ký tối thiểu khi chèn vào các bảng được lập chỉ mục


14

Tôi đang kiểm tra các bản ghi nhật ký tối thiểu trong các tình huống khác nhau và từ những gì tôi đã đọc CHERTN VÀO CHỌN thành một đống với chỉ mục không được phân cụm bằng TABLOCK và SQL Server 2016+, nên đăng nhập tối thiểu, tuy nhiên trong trường hợp của tôi, tôi đang nhận được đăng nhập đầy đủ. Cơ sở dữ liệu của tôi nằm trong mô hình khôi phục đơn giản và tôi thành công nhận được các bản chèn tối thiểu được ghi vào một đống mà không có chỉ mục và TABLOCK.

Tôi đang sử dụng bản sao lưu cũ của cơ sở dữ liệu Stack Overflow để kiểm tra và đã tạo một bản sao của bảng Bài viết với lược đồ sau ...

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)

Sau đó tôi cố gắng sao chép bảng bài viết vào bảng này ...

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

Từ việc xem fn_dblog và cách sử dụng tệp nhật ký, tôi có thể thấy tôi không nhận được nhật ký tối thiểu từ việc này. Tôi đã đọc các phiên bản trước năm 2016 yêu cầu cờ theo dõi 610 để đăng nhập tối thiểu vào các bảng được lập chỉ mục, tôi cũng đã thử thiết lập điều này nhưng vẫn không có niềm vui.

Tôi đoán tôi đang thiếu một cái gì đó ở đây?

EDIT - Thông tin thêm

Để thêm thông tin Tôi đang sử dụng quy trình sau đây mà tôi đã viết để cố gắng phát hiện ghi nhật ký tối thiểu, có thể tôi đã gặp sự cố ở đây ...

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

Chèn vào một đống không có chỉ mục và TABLOCK bằng mã sau ...

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

Tôi nhận được những kết quả này

nhập mô tả hình ảnh ở đây

Với tốc độ tăng trưởng tệp nhật ký 0,0024mb, kích thước bản ghi nhật ký rất nhỏ và rất ít trong số đó tôi rất vui vì điều này đang sử dụng ghi nhật ký tối thiểu.

Nếu sau đó tôi tạo một chỉ mục không được nhóm trên id ...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

Sau đó chạy lại chèn của tôi một lần nữa ...

nhập mô tả hình ảnh ở đây

Tôi không chỉ không nhận được đăng nhập tối thiểu vào chỉ mục không được nhóm mà tôi còn mất nó trong heap. Sau khi thực hiện thêm một số thử nghiệm, có vẻ như nếu tôi tạo ID cụm thì nó sẽ ghi nhật ký tối thiểu nhưng từ những gì tôi đã đọc 2016+ nên đăng nhập tối thiểu vào một đống với chỉ mục không được phân cụm khi sử dụng khóa tab.

EDIT CUỐI CÙNG :

Tôi đã báo cáo hành vi cho Microsoft trên SQL Server UserVoice và sẽ cập nhật nếu tôi nhận được phản hồi. Tôi cũng đã viết chi tiết đầy đủ về các kịch bản nhật ký tối thiểu mà tôi không thể làm việc tại https://gavindraper.com/2018/05/29/Query-Server-Minimal-Logging-Inserts/


Câu trả lời:


12

Tôi có thể sao chép kết quả của bạn trên SQL Server 2017 bằng cơ sở dữ liệu Stack Overflow 2010, nhưng không (tất cả) kết luận của bạn.

Đăng nhập tối thiểu vào heap là không có sẵn khi sử dụng INSERT...SELECTvới TABLOCKmột heap với chỉ mục không bao gồm, điều này là bất ngờ . Tôi đoán là INSERT...SELECTkhông thể hỗ trợ tải số lượng lớn bằng cách sử dụng RowsetBulk(heap) cùng lúc với FastLoadContext(b-cây). Chỉ Microsoft mới có thể xác nhận nếu đây là lỗi hoặc do thiết kế.

Các chỉ số nonclustered trên heap được tối thiểu đăng nhập (giả sử TF610 được bật, hoặc SQL Server 2016+ được sử dụng, cho phép FastLoadContext) với các chú ý sau đây:

  • Chỉ các hàng được chèn vào các trang mới được phân bổ mới được ghi lại tối thiểu.
  • Các hàng được thêm vào trang chỉ mục đầu tiên không được ghi lại tối thiểu, nếu chỉ mục trống khi bắt đầu hoạt động.

497 LOP_INSERT_ROWSmục được hiển thị cho chỉ mục không bao gồm tương ứng với trang đầu tiên của chỉ mục. Vì chỉ mục trống trước, các hàng này được ghi lại đầy đủ. Các hàng còn lại đều được ghi tối thiểu . Nếu cờ theo dõi tài liệu 692 được bật (2016+) để tắt FastLoadContext, tất cả các hàng chỉ mục không được bao gồm sẽ được ghi lại tối thiểu.


Tôi thấy rằng ghi nhật ký tối thiểu được áp dụng cho cả chỉ mục heap và không bao gồm khi tải hàng loạt cùng một bảng (có chỉ mục) bằng cách sử dụng BULK INSERTtừ một tệp:

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

Tôi lưu ý điều này cho đầy đủ. Tải hàng loạt sử INSERT...SELECTdụng các đường dẫn mã khác nhau, vì vậy thực tế các hành vi khác nhau không hoàn toàn bất ngờ.


Đối với đầy đủ chi tiết về khai thác gỗ tối thiểu sử dụng RowsetBulkFastLoadContextINSERT...SELECTnhìn thấy ba loạt phần của tôi trên SQLPerformance.com:

  1. Ghi nhật ký tối thiểu với CHỌN CHỌN LỰA CHỌN vào bảng Heap
  2. Ghi nhật ký tối thiểu với CHỌN CHỌN LỰA CHỌN vào các bảng được nhóm trống
  3. Ghi nhật ký tối thiểu với CHỌN LỰA CHỌN VÀ CHỌN Tải nhanh

Các kịch bản khác từ bài đăng trên blog của bạn

Bình luận được đóng lại vì vậy tôi sẽ giải quyết ngắn gọn ở đây.

Chỉ mục cụm trống với dấu vết 610 hoặc 2016+

Điều này được ghi lại tối thiểu bằng cách sử dụng FastLoadContextmà không có TABLOCK. Các hàng duy nhất được ghi lại đầy đủ là những hàng được chèn vào trang đầu tiên vì chỉ mục được nhóm trống khi bắt đầu giao dịch.

Chỉ mục được nhóm với dữ liệu và theo dõi 610 HOẶC 2016+

Điều này cũng được ghi lại tối thiểu bằng cách sử dụng FastLoadContext. Các hàng được thêm vào trang hiện tại được ghi lại đầy đủ, phần còn lại được ghi lại tối thiểu.

Chỉ mục được nhóm với các chỉ mục không làm mờ và TABLOCK hoặc theo dõi 610 / SQL 2016+

Điều này cũng có thể được ghi lại tối thiểu bằng cách sử dụng FastLoadContextmiễn là chỉ số không bao gồm được duy trì bởi một toán tử riêng biệt, DMLRequestSortđược đặt thành đúng và các điều kiện khác được đưa ra trong các bài đăng của tôi được đáp ứng.


2

Các tài liệu dưới đây là cũ nhưng vẫn là một đọc tuyệt vời.

Trong SQL 2016, cờ theo dõi 610 và ALLOW_PAGE_LOCKS được bật theo mặc định, nhưng ai đó có thể đã tắt chúng.

Hướng dẫn hiệu suất tải dữ liệu

(3) Tùy thuộc vào gói được chọn bởi trình tối ưu hóa, chỉ mục không bao gồm trên bảng có thể được ghi lại đầy đủ hoặc tối thiểu.

Tuyên bố CHỌN có thể là vấn đề vì bạn đã có TOP và ĐẶT HÀNG B .NG. Bạn đang chèn dữ liệu vào Bảng theo thứ tự khác với Chỉ mục, do đó SQL có thể thực hiện nhiều Sắp xếp trong nền.

CẬP NHẬT 2

Bạn thực sự có thể nhận được đăng nhập tối thiểu. Với TraceFlag 610 ON, Nhật ký hoạt động khác đi, SQL sẽ dành đủ không gian trong Nhật ký để thực hiện Quay lại nếu có sự cố, nhưng thực tế sẽ không sử dụng Nhật ký.

Đây có lẽ là tính không gian dành riêng (không sử dụng)

EXEC('DBCC SQLPERF(logspace)')

Mã này tách ra Dành riêng từ Được sử dụng

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

Tôi cho rằng ghi nhật ký tối thiểu (theo như Microsoft có liên quan) thực sự là về việc thực hiện số lượng IO ít nhất trên nhật ký, chứ không phải là bao nhiêu nhật ký được bảo lưu.

Hãy xem liên kết này .

CẬP NHẬT 1

Hãy thử sử dụng TABLOCKX thay vì TABLOCK. Với Tablock, bạn vẫn có một khóa chung, vì vậy SQL có thể đang đăng nhập trong trường hợp một quá trình khác bắt đầu.

TABLOCK có thể cần được sử dụng cùng với HOLDLOCK. Điều này thực thi Tablock cho đến khi kết thúc giao dịch của bạn.

Đồng thời đặt khóa trên bảng nguồn [Bài viết], việc ghi nhật ký có thể diễn ra do bảng nguồn có thể thay đổi trong khi giao dịch của bạn đang diễn ra. Paul White đã đạt được ghi nhật ký tối thiểu khi nguồn không phải là bảng SQL.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.