เหตุใดฉันจึงไม่ได้รับการบันทึกขั้นต่ำเมื่อแทรกเข้าไปในตารางที่จัดทำดัชนี


14

ฉันกำลังทดสอบการแทรกการบันทึกน้อยที่สุดในสถานการณ์ที่แตกต่างกันและจากสิ่งที่ฉันอ่าน INSERT INTO SELECT เป็นฮีปที่มีดัชนีที่ไม่ใช่คลัสเตอร์โดยใช้ TABLOCK และ SQL Server 2016+ ควรบันทึกอย่างน้อยที่สุด แต่ในกรณีของฉันเมื่อทำสิ่งนี้ การบันทึกเต็มรูปแบบ ฐานข้อมูลของฉันอยู่ในรูปแบบการกู้คืนข้อมูลแบบง่ายและฉันได้รับการแทรกที่น้อยที่สุดใน heap โดยไม่มีดัชนีและ TABLOCK

ฉันใช้การสำรองข้อมูลเก่าของฐานข้อมูล Stack Overflow เพื่อทดสอบและได้สร้างการทำซ้ำของตารางโพสต์ด้วยสคีมาต่อไปนี้ ...

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)

ฉันลองคัดลอกตารางการโพสต์ลงในตารางนี้ ...

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

จากการดูที่ fn_dblog และการใช้งานไฟล์บันทึกฉันเห็นว่าฉันไม่ได้รับการบันทึกขั้นต่ำจากนี้ ฉันอ่านว่าเวอร์ชันก่อนปี 2559 ต้องใช้ค่าสถานะการติดตาม 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

แก้ไขครั้งสุดท้าย :

ฉันได้รายงานพฤติกรรมไปยัง Microsoft ในSQL Server UserVoiceและจะอัปเดตหากฉันได้รับคำตอบ ผมเคยเขียนถึงรายละเอียดทั้งหมดของสถานการณ์เข้าสู่ระบบน้อยที่สุดที่ฉันไม่สามารถได้รับการทำงานที่https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/


3
พอลไวท์มีคำตอบที่เป็นประโยชน์ที่เกี่ยวข้องที่นี่
Erik Darling

คำตอบ:


12

ฉันสามารถทำซ้ำผลลัพธ์ของคุณใน SQL Server 2017 โดยใช้ฐานข้อมูล Stack Overflow 2010 แต่ไม่ใช่ (ทั้งหมด) ข้อสรุปของคุณ

การเข้าสู่ระบบน้อยที่สุดเพื่อกองไม่สามารถใช้งานได้เมื่อใช้INSERT...SELECTกับTABLOCKการกองที่มีค่าดัชนี nonclustered ซึ่งเป็นที่ไม่คาดคิด ฉันเดาว่าINSERT...SELECTไม่สามารถรองรับการโหลดจำนวนมากโดยใช้RowsetBulk(ฮีป) พร้อมกันกับFastLoadContext(ต้นไม้ b) Microsoft เท่านั้นที่จะสามารถยืนยันได้ว่านี่เป็นข้อผิดพลาดหรือจากการออกแบบ

ดัชนี nonclusteredในกองจะเข้าสู่ระบบน้อยที่สุด (สมมติ TF610 อยู่บนหรือ SQL Server 2016+ จะใช้ในการเปิดใช้งานFastLoadContext) ที่มีคำเตือนต่อไปนี้:

  • เฉพาะแถวที่แทรกในเพจที่ถูกจัดสรรใหม่เท่านั้นที่จะถูกบันทึกอย่างน้อยที่สุด
  • แถวที่เพิ่มไปยังหน้าดัชนีแรกนั้นไม่ได้ถูกบันทึกไว้อย่างน้อยถ้าดัชนีว่างเปล่าเมื่อเริ่มต้นการดำเนินการ

LOP_INSERT_ROWSรายการ497 ที่แสดงสำหรับดัชนีที่ไม่เป็นคลัสเตอร์นั้นสอดคล้องกับหน้าแรกของดัชนี เนื่องจากดัชนีว่างก่อนหน้าแถวเหล่านี้จึงถูกบันทึกอย่างสมบูรณ์ แถวที่เหลือทั้งหมดเข้าสู่ระบบน้อยที่สุด หากเปิดใช้งานการตั้งค่าสถานะการติดตามเอกสาร 692 (2016+) เพื่อปิดใช้งานFastLoadContextแถวดัชนีที่ไม่ใช่คลัสเตอร์ทั้งหมดจะถูกบันทึกอย่างน้อยที่สุด


ฉันพบว่าการบันทึกที่น้อยที่สุดถูกนำไปใช้กับทั้งฮีปและดัชนีที่ไม่คลัสเตอร์เมื่อโหลดจำนวนมากในตารางเดียวกัน (พร้อมดัชนี) โดยใช้BULK INSERTจากไฟล์:

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

ฉันบันทึกสิ่งนี้เพื่อความสมบูรณ์ การโหลดจำนวนมากโดยINSERT...SELECTใช้เส้นทางของรหัสที่แตกต่างกันดังนั้นความจริงที่ว่าพฤติกรรมที่แตกต่างนั้นไม่ได้เกิดขึ้นอย่างคาดไม่ถึง


สำหรับรายละเอียดเกี่ยวกับการบันทึกที่น้อยที่สุดโดยใช้RowsetBulkและFastLoadContextด้วยINSERT...SELECTชุดข้อมูลสามส่วนของฉันใน SQLPerformance.com:

  1. การบันทึกขั้นต่ำด้วย INSERT …เลือกลงในตารางฮีป
  2. การบันทึกขั้นต่ำด้วย INSERT … SELECT ลงในตารางที่ทำคลัสเตอร์ว่างเปล่า
  3. การบันทึกที่น้อยที่สุดด้วย INSERT … SELECT และบริบทการโหลดที่รวดเร็ว

สถานการณ์อื่น ๆ จากการโพสต์บล็อกของคุณ

ความคิดเห็นจะปิดดังนั้นฉันจะพูดสั้น ๆ เหล่านี้ที่นี่

ดัชนีที่ว่างเปล่าแบบกลุ่มด้วยการติดตาม 610 หรือ 2016+

นี้ถูกบันทึกไว้น้อยที่สุดใช้โดยไม่ต้องFastLoadContext TABLOCKแถวเดียวที่ถูกบันทึกอย่างสมบูรณ์คือแถวที่ถูกแทรกในหน้าแรกเนื่องจากดัชนีคลัสเตอร์ว่างเปล่าเมื่อเริ่มต้นธุรกรรม

ดัชนีแบบกลุ่มที่มีข้อมูลและติดตาม 610 หรือ 2016+

FastLoadContextนี้ยังเป็นลงทะเบียนน้อยที่สุดโดยใช้ แถวที่ถูกเพิ่มในเพจที่มีอยู่จะถูกบันทึกอย่างสมบูรณ์ส่วนที่เหลือจะถูกบันทึกอย่างน้อยที่สุด

ดัชนีแบบคลัสเตอร์ที่มีดัชนีแบบไม่รวมกลุ่มและ TABLOCK หรือติดตาม 610 / SQL 2016+

นี้ยังสามารถลงทะเบียนน้อยที่สุดโดยใช้FastLoadContextตราบใดที่ดัชนี nonclustered จะดูแลโดยผู้ประกอบการที่แยกจากกันDMLRequestSortกำหนดเป็นจริงและเงื่อนไขอื่น ๆ ออกมาวางในการโพสต์ของฉันจะได้พบกับ


2

เอกสารด้านล่างเก่า แต่ก็ยังยอดเยี่ยมอ่าน

ในการตั้งค่าสถานะการสืบค้นกลับ SQL 2016 610 และ ALLOW_PAGE_LOCKS โดยค่าเริ่มต้น แต่บางคนอาจปิดใช้งาน

คู่มือประสิทธิภาพการโหลดข้อมูล

(3) ขึ้นอยู่กับแผนที่เลือกโดยเครื่องมือเพิ่มประสิทธิภาพดัชนีที่ไม่ได้จัดกลุ่มบนตารางอาจถูกบันทึกอย่างสมบูรณ์หรือน้อยที่สุด

คำสั่ง SELECT อาจเป็นปัญหาเนื่องจากคุณมี TOP และ ORDER BY คุณกำลังแทรกข้อมูลลงในตารางตามลำดับที่แตกต่างกับดัชนีดังนั้น 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

ลองใช้ TABLOCKX แทน TABLOCK ด้วย Tablock คุณยังคงมีการล็อคที่ใช้ร่วมกันดังนั้น SQL อาจเข้าสู่ระบบในกรณีที่กระบวนการอื่นเริ่มต้นขึ้น

อาจต้องใช้ TABLOCK ร่วมกับ HOLDLOCK สิ่งนี้บังคับใช้ Tablock จนกว่าจะสิ้นสุดธุรกรรมของคุณ

ใส่ล็อกไว้ในตารางต้นฉบับ [โพสต์] การบันทึกอาจเกิดขึ้นเพราะตารางต้นฉบับอาจเปลี่ยนแปลงได้ในขณะที่ธุรกรรมของคุณกำลังเกิดขึ้น Paul White ประสบความสำเร็จในการบันทึกขั้นต่ำเมื่อแหล่งที่มาไม่ใช่ตาราง SQL

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