การประเมินความผิดปกติที่ไม่ดีทำให้ขาดคุณสมบัติ INSERT จากการบันทึกที่น้อยที่สุด?


11

ทำไมข้อความที่สองINSERT~ 5x ช้ากว่าประโยคแรก

จากจำนวนข้อมูลบันทึกที่สร้างขึ้นฉันคิดว่าข้อมูลที่สองไม่ผ่านการรับรองสำหรับการบันทึกขั้นต่ำ อย่างไรก็ตามเอกสารประกอบในคู่มือประสิทธิภาพการโหลดข้อมูลระบุว่าส่วนแทรกทั้งสองควรสามารถบันทึกได้น้อยที่สุด ดังนั้นหากการบันทึกขั้นต่ำเป็นความแตกต่างของประสิทธิภาพที่สำคัญทำไมจึงเป็นไปได้ว่าการสืบค้นครั้งที่สองไม่มีคุณสมบัติสำหรับการบันทึกขั้นต่ำ สิ่งที่สามารถทำได้เพื่อปรับปรุงสถานการณ์?


Query # 1: การแทรกแถว 5 มม. โดยใช้ INSERT ... with (TABLOCK)

พิจารณาแบบสอบถามต่อไปนี้ซึ่งแทรกแถว 5MM ลงในกอง แบบสอบถามนี้ดำเนินการใน1 secondและสร้างข้อมูลล็อกธุรกรรมตามการรายงานของ64MBsys.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


Query # 2: การแทรกข้อมูลเดียวกัน แต่ SQL จะประเมิน # ของแถวต่ำกว่า

ตอนนี้ให้ลองพิจารณาคำถามที่คล้ายกันนี้ซึ่งทำงานกับข้อมูลเดียวกัน แต่เกิดขึ้นจากตาราง (หรือSELECTคำสั่งที่ซับซ้อนที่มีการรวมหลายรายการในกรณีการผลิตจริงของฉัน) ซึ่งการประเมิน cardinality ต่ำเกินไป แบบสอบถามนี้ดำเนินการ5.5 secondsและสร้าง461MBข้อมูลบันทึกธุรกรรม

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


สคริปต์เต็ม

ดูPastebin นี้เพื่อดูชุดสคริปต์เพื่อสร้างข้อมูลการทดสอบและดำเนินการตามสถานการณ์เหล่านี้ ทราบว่าคุณต้องใช้ฐานข้อมูลที่อยู่ในที่รูปแบบการกู้คืนSIMPLE


บริบททางธุรกิจ

เราเคลื่อนย้ายข้อมูลไปรอบ ๆ แถวข้อมูลหลายล้านแถวและสิ่งสำคัญคือการให้การดำเนินการเหล่านี้มีประสิทธิภาพมากที่สุดเท่าที่จะเป็นไปได้ทั้งในแง่ของเวลาดำเนินการและโหลดดิสก์ I / O เริ่มแรกเราอยู่ภายใต้ความประทับใจว่าการสร้างตารางฮีปและการใช้INSERT...WITH (TABLOCK)เป็นวิธีที่ดีในการทำสิ่งนี้ แต่ตอนนี้เรามีความมั่นใจน้อยลงเนื่องจากเราสังเกตเห็นสถานการณ์ที่แสดงข้างต้นในสถานการณ์การผลิตจริง (แม้ว่าจะมีแบบสอบถามที่ซับซ้อนมากกว่า เวอร์ชั่นที่เรียบง่ายที่นี่)

คำตอบ:


7

เพราะเหตุใดแบบสอบถามที่สองจึงไม่เข้าเกณฑ์สำหรับการบันทึกขั้นต่ำ

การบันทึกขั้นต่ำสุดมีให้สำหรับเคียวรีที่สอง แต่เอ็นจิ้นเลือกที่จะไม่ใช้เมื่อรันไทม์

มีเกณฑ์ขั้นต่ำสำหรับINSERT...SELECTด้านล่างซึ่งเลือกที่จะไม่ใช้การปรับให้เหมาะสมกับโหลดจำนวนมาก มีค่าใช้จ่ายที่เกี่ยวข้องในการตั้งค่าการดำเนินการ rowset จำนวนมากและการแทรกจำนวนมากเพียงไม่กี่แถวจะไม่ส่งผลให้การใช้พื้นที่มีประสิทธิภาพ

สิ่งที่สามารถทำได้เพื่อปรับปรุงสถานการณ์?

ใช้หนึ่งในวิธีอื่น ๆ อีกมากมาย (เช่นSELECT INTO) ที่ไม่มีเกณฑ์นี้ หรือคุณอาจจะสามารถเขียนแบบสอบถามแหล่งที่มาในทางที่จะเพิ่มจำนวนโดยประมาณของแถวหน้า / INSERT...SELECTกว่าเกณฑ์สำหรับบางคน

ดูคำตอบด้วยตนเองของ Geoffสำหรับข้อมูลที่เป็นประโยชน์เพิ่มเติม


อาจจะเป็นเรื่องไม่สำคัญที่น่าสนใจ: SET STATISTICS IOรายงานตรรกะอ่านสำหรับตารางเป้าหมายเฉพาะเมื่อกลุ่มการเพิ่มประสิทธิภาพในการโหลดไม่ได้ใช้


5

ฉันสามารถสร้างปัญหาด้วยอุปกรณ์ทดสอบของฉันเอง:

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;

นี่เป็นคำถามทำไมไม่ "แก้ไข" ปัญหาโดยการปรับปรุงสถิติในตารางแหล่งที่มาก่อนที่จะเรียกใช้การดำเนินการเข้าสู่ระบบน้อยที่สุด?

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
ในโค้ดจริงมีความซับซ้อนคำสั่งที่มีจำนวนมากร่วมที่สร้างผลการตั้งค่าสำหรับSELECT INSERTการรวมเหล่านี้สร้างการประมาณค่าความเป็นหัวใจที่ไม่ดีสำหรับตัวดำเนินการแทรกตารางสุดท้าย (ซึ่งฉันได้จำลองไว้ในสคริปต์ repro ผ่านการUPDATE STATISTICSโทรที่ไม่ดี) และดังนั้นจึงไม่ใช่เรื่องง่ายเหมือนการออกUPDATE STATISTICSคำสั่งเพื่อแก้ไขปัญหา ฉันเห็นด้วยอย่างยิ่งว่าการทำให้แบบสอบถามง่ายขึ้นเพื่อให้เข้าใจว่า Cardinality Estimator เข้าใจได้ง่ายขึ้นอาจเป็นวิธีที่ดี แต่ไม่ใช่วิธีการที่ซับซ้อนในการใช้ตรรกะทางธุรกิจที่ซับซ้อน
Geoff Patterson

ฉันไม่มีอินสแตนซ์ของ SQL Server 2014 เพื่อทดสอบสิ่งนี้อย่างไรก็ตามการระบุปัญหาเกี่ยวกับ Cardinality Estimator ใหม่ของ SQL Server 2014 และการปรับปรุง Service Pack 1พูดถึงการเปิดใช้งานการตั้งค่าสถานะการสืบค้นกลับ 4199 ท่ามกลางคนอื่น ๆ เพื่อเปิดใช้งาน คุณเคยลองไหม
Max Vernon

ความคิดที่ดี แต่มันก็ไม่ได้ช่วย ฉันเพิ่งลอง TF 4199, TF 610 (คลายเงื่อนไขการบันทึกขั้นต่ำให้น้อยที่สุด) และทั้งสองอย่างเข้าด้วยกัน (เฮ้ทำไมไม่ล่ะ?) แต่ไม่มีการเปลี่ยนแปลงสำหรับคิวรีทดสอบครั้งที่สอง
Geoff Patterson

4

เขียนคำสืบค้นต้นฉบับอีกครั้งเพื่อเพิ่มจำนวนแถวโดยประมาณ

ขยายความคิดของ Paul วิธีแก้ปัญหาหากคุณหมดหวังอย่างแท้จริงคือการเพิ่มตารางดัมมี่ที่รับประกันได้ว่าจำนวนแถวของเม็ดมีดโดยประมาณจะสูงพอที่จะมีคุณภาพสำหรับการเพิ่มประสิทธิภาพการโหลดจำนวนมาก ฉันยืนยันว่านี่เป็นการบันทึกขั้นต่ำและปรับปรุงประสิทธิภาพการสืบค้น

-- 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)

ประเด็นสุดท้าย

  1. ใช้SELECT...INTOสำหรับการดำเนินการแทรกครั้งเดียวหากต้องการบันทึกน้อยที่สุด ดังที่พอลชี้ให้เห็นสิ่งนี้จะช่วยให้มั่นใจว่ามีการบันทึกน้อยที่สุดโดยไม่คำนึงถึงการประมาณแถว
  2. เมื่อใดก็ตามที่เป็นไปได้ให้เขียนคิวรีด้วยวิธีง่ายๆที่เครื่องมือเพิ่มประสิทธิภาพคิวรีสามารถให้เหตุผลได้อย่างมีประสิทธิภาพ อาจเป็นไปได้ที่จะแยกแบบสอบถามออกเป็นหลาย ๆ ชิ้นตัวอย่างเช่นเพื่อให้สามารถสร้างสถิติบนโต๊ะกลางได้
  3. หากคุณมีสิทธิ์เข้าถึง SQL Server 2014 ให้ลองใช้กับแบบสอบถามของคุณ ในกรณีการผลิตจริงของฉันฉันเพิ่งลองและเครื่องมือประมาณการ Cardinality ใหม่ให้ผลการประมาณการที่สูงขึ้น (และดีกว่า) มากขึ้น แบบสอบถามนั้นถูกบันทึกอย่างน้อยที่สุด แต่สิ่งนี้อาจไม่เป็นประโยชน์หากคุณต้องการรองรับ SQL 2012 และรุ่นก่อนหน้า
  4. หากคุณหมดหวังวิธีแก้ปัญหาแบบแฮ็คอาจใช้ได้!

บทความที่เกี่ยวข้อง

Paul White's พฤษภาคม 2019 บล็อกโพสต์การบันทึกอย่างน้อยด้วย INSERT …เลือกลงใน Heap Tablesครอบคลุมข้อมูลบางส่วนในรายละเอียดเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.