Ước tính cardinality kém loại bỏ INSERT khỏi đăng nhập tối thiểu?


11

Tại sao INSERTcâu lệnh thứ hai ~ 5x chậm hơn câu lệnh thứ nhất?

Từ số lượng dữ liệu nhật ký được tạo, tôi nghĩ rằng thứ hai không đủ điều kiện để ghi nhật ký tối thiểu. Tuy nhiên, tài liệu trong Hướng dẫn hiệu suất tải dữ liệu chỉ ra rằng cả hai phần chèn sẽ có thể được ghi lại tối thiểu. Vì vậy, nếu ghi nhật ký tối thiểu là sự khác biệt hiệu suất chính, tại sao truy vấn thứ hai không đủ điều kiện để ghi nhật ký tối thiểu? Có thể làm gì để cải thiện tình hình?


Truy vấn # 1: Chèn các hàng 5MM bằng CHERTN ... VỚI (TABLOCK)

Hãy xem xét các truy vấn sau, trong đó chèn các hàng 5MM thành một đống. Truy vấn này thực hiện trong 1 secondvà tạo 64MBdữ liệu nhật ký giao dịch như được báo cáo bởi sys.dm_tran_database_transactions.

CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO


Truy vấn # 2: Chèn cùng một dữ liệu, nhưng SQL đánh giá thấp # của các hàng

Bây giờ hãy xem xét truy vấn rất giống nhau này, hoạt động trên cùng một dữ liệu chính xác nhưng tình cờ rút ra từ một bảng (hoặc SELECTcâu lệnh phức tạp có nhiều phép nối trong trường hợp sản xuất thực tế của tôi) trong đó ước tính cardinality quá thấp. Truy vấn này thực hiện trong 5.5 secondsvà tạo 461MBdữ liệu nhật ký giao dịch.

CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO


Kịch bản đầy đủ

Xem Pastebin này để biết tập hợp đầy đủ các tập lệnh để tạo dữ liệu thử nghiệm và thực hiện một trong các kịch bản này. Lưu ý rằng bạn phải sử dụng cơ sở dữ liệu trong SIMPLE mô hình khôi phục .


Bối cảnh kinh doanh

Chúng tôi thường xuyên di chuyển xung quanh hàng triệu hàng dữ liệu và điều quan trọng là các hoạt động này phải hiệu quả nhất có thể, cả về thời gian thực hiện và tải I / O của đĩa. Ban đầu, chúng tôi đã có ấn tượng rằng việc tạo một bảng heap và sử dụng INSERT...WITH (TABLOCK)là một cách tốt để làm điều này, nhưng giờ đã trở nên kém tự tin hơn khi chúng tôi quan sát tình huống được trình bày ở trên trong một kịch bản sản xuất thực tế (mặc dù với các truy vấn phức tạp hơn, không phải là phiên bản đơn giản hóa tại đây).

Câu trả lời:


7

Tại sao truy vấn thứ hai không đủ điều kiện để đăng nhập tối thiểu?

Ghi nhật ký tối thiểu có sẵn cho truy vấn thứ hai, nhưng công cụ chọn không sử dụng nó trong thời gian chạy.

Có một ngưỡng tối thiểu cho INSERT...SELECTdưới đây mà nó chọn không sử dụng tối ưu hóa tải số lượng lớn. Có một chi phí liên quan đến việc thiết lập một hoạt động hàng loạt và chỉ chèn một số lượng lớn sẽ không dẫn đến việc sử dụng không gian hiệu quả.

Có thể làm gì để cải thiện tình hình?

Sử dụng một trong nhiều phương pháp khác (ví dụ SELECT INTO) không có ngưỡng này. Ngoài ra, bạn có thể viết lại truy vấn nguồn theo một cách nào đó để tăng số lượng hàng / trang ước tính vượt ngưỡng cho INSERT...SELECT.

Xem thêm tự trả lời của Geoff để biết thêm thông tin hữu ích.


Câu đố có thể thú vị: chỉ SET STATISTICS IO báo cáo các lần đọc logic cho bảng đích khi tối ưu hóa tải hàng loạt không được sử dụng .


5

Tôi đã có thể tạo lại vấn đề với thiết bị thử nghiệm của riêng mình:

USE test;

CREATE TABLE dbo.SourceGood
(
    SourceGoodID INT NOT NULL
        CONSTRAINT PK_SourceGood
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , SomeData VARCHAR(384) NOT NULL
);

CREATE TABLE dbo.SourceBad
(
    SourceBadID INT NOT NULL
        CONSTRAINT PK_SourceBad
        PRIMARY KEY CLUSTERED
        IDENTITY(-2147483647,1)
    , SomeData VARCHAR(384) NOT NULL
);

CREATE TABLE dbo.InsertTest
(
    SourceBadID INT NOT NULL
        CONSTRAINT PK_InsertTest
        PRIMARY KEY CLUSTERED
    , SomeData VARCHAR(384) NOT NULL
);
GO

INSERT INTO dbo.SourceGood WITH (TABLOCK) (SomeData) 
SELECT TOP(5000000) o.name + o1.name + o2.name
FROM syscolumns o
    , syscolumns o1
    , syscolumns o2;
GO

ALTER DATABASE test SET AUTO_UPDATE_STATISTICS OFF;
GO

INSERT INTO dbo.SourceBad WITH (TABLOCK) (SomeData)
SELECT TOP(5000000) o.name + o1.name + o2.name
FROM syscolumns o
    , syscolumns o1
    , syscolumns o2;
GO

ALTER DATABASE test SET AUTO_UPDATE_STATISTICS ON;
GO

BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceGood;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count
472 
database_transaction_log_bytes_used
692136
*/

COMMIT TRANSACTION;


BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceBad;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count   
5000003 
database_transaction_log_bytes_used
642699256
*/

COMMIT TRANSACTION;

Điều này đặt ra câu hỏi, tại sao không "khắc phục" vấn đề bằng cách cập nhật số liệu thống kê trên các bảng nguồn trước khi chạy hoạt động được ghi lại tối thiểu?

TRUNCATE TABLE dbo.InsertTest;
UPDATE STATISTICS dbo.SourceBad;

BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceBad;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count
472
database_transaction_log_bytes_used
692136
*/

COMMIT TRANSACTION;

2
Trong mã thực, có một SELECTcâu lệnh phức tạp với nhiều phép nối tạo ra tập kết quả cho INSERT. Các phép nối này tạo ra các ước tính cardinality kém cho toán tử chèn bảng cuối cùng (mà tôi đã mô phỏng trong tập lệnh repro thông qua UPDATE STATISTICScuộc gọi xấu ), và do đó không đơn giản như ban hành UPDATE STATISTICSlệnh để khắc phục sự cố. Tôi hoàn toàn đồng ý rằng việc đơn giản hóa truy vấn để Công cụ ước tính Cardinality dễ hiểu hơn có thể là một cách tiếp cận tốt, nhưng nó không phải là một trival để thực hiện logic kinh doanh phức tạp.
Geoff Patterson

Tôi không có một trường hợp SQL Server 2014 để kiểm tra điều này trên, tuy nhiên Xác định SQL Server 2014 các vấn đề mới Cardinality Ước tính và Service Pack 1 cải thiện cuộc đàm phán về cho phép theo dõi cờ 4199, trong số những người khác, để cho phép ước lượng cardinality mới. Bạn đã thử chưa?
Max Vernon

Ý tưởng tốt, nhưng nó không giúp được gì. Tôi vừa thử TF 4199, TF 610 (nới lỏng các điều kiện ghi nhật ký tối thiểu) và cả hai cùng nhau (hey, tại sao không?), Nhưng không thay đổi cho truy vấn thử nghiệm thứ 2.
Geoff Patterson

4

Viết lại truy vấn nguồn theo cách nào đó để tăng số lượng hàng ước tính

Mở rộng theo ý tưởng của Paul, một cách giải quyết nếu bạn thực sự tuyệt vọng là thêm một bảng giả đảm bảo rằng số lượng hàng ước tính cho phần chèn sẽ đủ cao để đảm bảo chất lượng tối ưu hóa tải hàng loạt. Tôi xác nhận rằng điều này được ghi nhật ký tối thiểu và cải thiện hiệu suất truy vấn.

-- Create a dummy table that SQL Server thinks has a million rows
CREATE TABLE dbo.emptyTableWithMillionRowEstimate (
    n INT PRIMARY KEY
)
GO
UPDATE STATISTICS dbo.emptyTableWithMillionRowEstimate
WITH ROWCOUNT = 1000000
GO

-- Concatenate this table into the final rowset:
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Add in dummy rowset to ensure row estimate is high enough for bulk load optimization
UNION ALL
SELECT NULL FROM dbo.emptyTableWithMillionRowEstimate
OPTION (MAXDOP 1)

Đón cuối

  1. Sử dụng SELECT...INTOcho các hoạt động chèn một lần nếu yêu cầu đăng nhập tối thiểu. Như Paul chỉ ra, điều này sẽ đảm bảo ghi nhật ký tối thiểu bất kể ước tính hàng
  2. Bất cứ nơi nào có thể, hãy viết các truy vấn một cách đơn giản mà trình tối ưu hóa truy vấn có thể giải thích một cách hiệu quả. Ví dụ, có thể chia một truy vấn thành nhiều phần để cho phép xây dựng số liệu thống kê trên một bảng trung gian.
  3. Nếu bạn có quyền truy cập vào SQL Server 2014, hãy dùng thử truy vấn của bạn; trong trường hợp sản xuất thực tế của tôi, tôi vừa thử nó và Công cụ ước tính Cardinality mới mang lại ước tính cao hơn (và tốt hơn); truy vấn sau đó được ghi lại tối thiểu. Nhưng điều này có thể không hữu ích nếu bạn cần hỗ trợ SQL 2012 trở về trước.
  4. Nếu bạn đang tuyệt vọng, các giải pháp hacky như thế này có thể được áp dụng!

Một bài viết liên quan

Bài đăng trên blog của Paul White vào tháng 5 năm 2019 Đăng nhập tối thiểu với CHERTN CHỌN vào Bảng Heap bao gồm một số thông tin này chi tiết hơn.

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.