เหตุใดการเปลี่ยนแปลงคอลัมน์ไม่เป็นศูนย์ทำให้เกิดการเติบโตของไฟล์บันทึกขนาดใหญ่


56

ฉันมีตารางที่มีแถว 64 ม. ที่ใช้ดิสก์ 4.3 GB สำหรับข้อมูล

แต่ละแถวมีประมาณ 30 ไบต์ของคอลัมน์จำนวนเต็มบวกNVARCHAR(255)คอลัมน์ตัวแปรสำหรับข้อความ

ฉันเพิ่ม AA คอลัมน์ nullable Datetimeoffset(0)กับข้อมูลประเภท

จากนั้นฉันก็อัพเดทคอลัมน์นี้สำหรับทุกแถวและตรวจสอบให้แน่ใจว่าเม็ดมีดใหม่ทั้งหมดวางค่าในคอลัมน์นี้

เมื่อไม่มีรายการ NULL ฉันก็วิ่งคำสั่งนี้เพื่อให้ฟิลด์ใหม่ของฉันได้รับคำสั่ง:

ALTER TABLE tblCheckResult 
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL

ผลที่ได้คือการเติบโตอย่างมากในขนาดของบันทึกการทำธุรกรรม - จาก 6GB เป็นมากกว่า 36GB จนกว่าจะหมดพื้นที่!

ไม่มีใครมีความคิดอะไรในโลก SQL Server 2008 R2 ที่กำลังทำอยู่สำหรับคำสั่งง่ายๆนี้เพื่อให้เกิดการเติบโตอย่างมาก


7
SQL Server 2012 Enterprise เพิ่มความสามารถในการเพิ่มNOT NULLคอลัมน์ที่มีค่าเริ่มต้นเป็นการดำเนินการข้อมูลเมตา ยังเห็น "การเพิ่มคอลัมน์ไม่เป็นโมฆะเป็นการดำเนินงานออนไลน์" ในเอกสาร
พอลไวท์

คำตอบ:


48

เมื่อคุณเปลี่ยนคอลัมน์เป็น NOT NULL, SQL Server จะต้องแตะทุก ๆหน้าแม้ว่าจะไม่มีค่า NULL ก็ตาม ขึ้นอยู่กับปัจจัยการส่งของคุณสิ่งนี้อาจนำไปสู่การแยกหน้าได้เป็นจำนวนมาก แน่นอนว่าทุกหน้าที่ถูกแตะต้องเข้าสู่ระบบและฉันสงสัยว่าเนื่องจากการแยกที่ต้องทำการเปลี่ยนแปลงสองรายการสำหรับหลาย ๆ หน้า แม้ว่าจะทำเสร็จในรอบเดียว แต่บันทึกจะต้องพิจารณาการเปลี่ยนแปลงทั้งหมดเพื่อที่ว่าหากคุณกดยกเลิกก็จะรู้ว่าจะยกเลิกอะไร


ตัวอย่าง. ตารางง่าย ๆ :

DROP TABLE dbo.floob;
GO

CREATE TABLE dbo.floob
(
  id INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, 
  bar INT NULL
);

INSERT dbo.floob(bar) SELECT NULL UNION ALL SELECT 4 UNION ALL SELECT NULL;

ALTER TABLE dbo.floob ADD CONSTRAINT df DEFAULT(0) FOR bar

ตอนนี้เรามาดูรายละเอียดหน้า ก่อนอื่นเราต้องค้นหาว่าเพจใดและ DB_ID ที่เราติดต่อด้วย ในกรณีของฉันฉันสร้างฐานข้อมูลที่เรียกว่าfooและ DB_ID เกิดขึ้นเป็น 5

DBCC TRACEON(3604, -1);
DBCC IND('foo', 'dbo.floob', 1);
SELECT DB_ID();

ผลลัพธ์แสดงว่าฉันสนใจหน้า 159 (แถวเดียวในDBCC INDเอาต์พุตด้วยPageType = 1)

ตอนนี้เรามาดูรายละเอียดหน้าเลือกบางอย่างเมื่อเราผ่านสถานการณ์ของ OP

DBCC PAGE(5, 1, 159, 3);

ป้อนคำอธิบายรูปภาพที่นี่

UPDATE dbo.floob SET bar = 0 WHERE bar IS NULL;    
DBCC PAGE(5, 1, 159, 3);

ป้อนคำอธิบายรูปภาพที่นี่

ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
DBCC PAGE(5, 1, 159, 3);

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้ฉันไม่มีคำตอบทั้งหมดสำหรับเรื่องนี้เพราะฉันไม่ใช่คนเจ้าเล่ห์ แต่เป็นที่ชัดเจนว่า - ในขณะที่ทั้งการดำเนินการอัปเดตและการเพิ่มข้อ จำกัด NOT NULL อย่างปฏิเสธไม่ได้เขียนไปยังหน้า - หลังทำในลักษณะที่แตกต่างกันอย่างสิ้นเชิง ดูเหมือนว่าจะเปลี่ยนโครงสร้างของเรกคอร์ดแทนที่จะเป็นเพียงซอกับบิตโดยการสลับคอลัมน์ nullable สำหรับคอลัมน์ที่ไม่ใช่ null ทำไมต้องทำเช่นนั้นฉันไม่แน่ใจ - คำถามที่ดีสำหรับทีมเก็บข้อมูลฉันเดา ฉันเชื่อว่า SQL Server 2012 จัดการกับสถานการณ์เหล่านี้ได้ดีขึ้นมาก FWIW - แต่ฉันยังไม่ได้ทำการทดสอบใด ๆ


4
พฤติกรรมนี้เปลี่ยนไปอย่างมากใน SQL Server รุ่นที่ใหม่กว่า ฉันตรวจสอบ 2016 RC2 แล้วและพบว่าสำหรับสถานการณ์ที่แน่นอนนี้และ 1 ล้านแถวในตารางมีการสร้างบันทึก 29 รายการระหว่างการเปลี่ยนจาก NULL เป็น NOT NULL หากค่าทั้งหมดถูกระบุไว้แล้วสำหรับคอลัมน์
Endrju

32

เมื่อดำเนินการคำสั่ง

ALTER COLUMN ... NOT NULL

สิ่งนี้ดูเหมือนว่าจะถูกนำไปใช้เป็นการเพิ่มคอลัมน์, ปรับปรุง, การดำเนินการของคอลัมน์ลดลง

  • แถวใหม่ถูกแทรกลงในsys.sysrscolsเพื่อเป็นตัวแทนคอลัมน์ใหม่ statusบิตสำหรับ128การตั้งค่าแสดงให้เห็นคอลัมน์ไม่อนุญาตให้NULLs
  • การอัปเดตจะดำเนินการกับทุกแถวของตารางการตั้งค่าคอลัมน์ columnn ใหม่ให้เป็นค่าของคอลัมน์ colum เก่า หากเวอร์ชัน "before" และ "after" ของแถวเหมือนกันนี่จะไม่ทำให้สิ่งใด ๆ ถูกเขียนลงในบันทึกของธุรกรรมมิฉะนั้นการอัพเดทจะถูกบันทึกไว้
  • คอลัมน์เดิมถูกทำเครื่องหมายเป็นลดลง (นี้เป็นเพียงข้อมูลเมตาของการเปลี่ยนแปลงในsys.sysrscols. rscolidการปรับปรุงเพื่อให้เป็นจำนวนเต็มขนาดใหญ่และstatusบิต 2 ชุดเพื่อชี้ให้เห็นลดลง)
  • รายการในsys.sysrscolsสำหรับคอลัมน์ใหม่นั้นได้รับการแก้ไขเพื่อให้rscolidเป็นคอลัมน์เก่า

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

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

สำหรับคอลัมน์ความยาวผันแปรที่จัดเก็บในFixedVarรูปแบบฉันพบว่าการตั้งค่าNOT NULLเป็นสาเหตุให้เกิดการเปลี่ยนแปลงในแถวที่ต้องบันทึกไว้เสมอ จำนวนคอลัมน์และจำนวนคอลัมน์ที่มีความยาวแปรผันทั้งคู่จะเพิ่มขึ้นและคอลัมน์ใหม่จะถูกเพิ่มเข้าไปที่ส่วนท้ายของส่วนความยาวตัวแปรที่ทำซ้ำข้อมูล

datetimeoffset(0)อย่างไรก็ตามความยาวคงที่และสำหรับคอลัมน์ความยาวคงที่ที่จัดเก็บในFixedVarรูปแบบคอลัมน์เก่าและใหม่ทั้งคู่ดูเหมือนจะได้รับช่องเดียวกันในส่วนข้อมูลความยาวคงที่ของแถวและเนื่องจากทั้งคู่มีความยาวและค่าเท่ากัน"ก่อน" และ "หลัง" รุ่นของแถวนี้เหมือนกัน สามารถเห็นได้ในคำตอบของ @ Aaron ทั้งสองรุ่นของแถวก่อนและหลังการALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;Are

0x10000c00 01000000 00000000 020000

สิ่งนี้ไม่ได้เข้าสู่ระบบ

เหตุผลจากคำอธิบายเหตุการณ์ของฉันจริง ๆ แล้วแถวควรจะแตกต่างกันที่นี่เนื่องจากจำนวนคอลัมน์02ควรเพิ่มเป็น03แต่ไม่มีการเปลี่ยนแปลงเช่นนี้เกิดขึ้นจริงในทางปฏิบัติ

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

  • หากมีการประกาศคอลัมน์ตั้งแต่แรกSPARSEแล้วคอลัมน์ใหม่จะถูกเก็บไว้ในส่วนต่าง ๆ ของแถวจากต้นฉบับซึ่งทำให้รูปภาพแถวก่อนและหลังแตกต่างกัน
  • หากคุณใช้ตัวเลือกการบีบอัดใด ๆ ตัวเลือกรุ่นก่อนหน้าและหลังของแถวจะแตกต่างกันเนื่องจากส่วนการนับคอลัมน์ในอาร์เรย์ซีดีจะเพิ่มขึ้น
  • บนฐานข้อมูลที่มีหนึ่งในตัวเลือกการแยกสแน็ปช็อตที่เปิดใช้งานแล้วข้อมูลการกำหนดเวอร์ชันในแต่ละแถวจะได้รับการอัพเดต (@ SQL Kiwi ชี้ให้เห็นว่าสิ่งนี้สามารถเกิดขึ้นได้ในฐานข้อมูลโดยไม่เปิดใช้ SI ตามที่อธิบายไว้ที่นี่ )
  • อาจมีALTER TABLEการดำเนินการก่อนหน้านี้บางส่วนที่นำมาใช้เป็นข้อมูลเมตาเท่านั้นที่เปลี่ยนแปลงและยังไม่ได้นำไปใช้กับแถว ตัวอย่างเช่นหากมีการเพิ่มคอลัมน์ความยาวผันแปรได้ที่เป็นโมฆะสิ่งนี้จะถูกนำไปใช้ตั้งแต่แรกเริ่มเนื่องจากเมทาดาทามีการเปลี่ยนแปลงเท่านั้นและจะถูกเขียนลงในแถวเมื่อมีการปรับปรุงครั้งถัดไปเท่านั้น (การเขียนที่เกิดขึ้นจริงในอินสแตนซ์สุดท้ายนี้ ส่วนการนับคอลัมน์และคอลัมน์ในNULL_BITMAPฐานะNULL varcharที่ส่วนท้ายของแถวไม่ใช้พื้นที่ใด ๆ )

5

ฉันประสบปัญหาเดียวกันเกี่ยวกับตารางที่มีจำนวน 200,000 แถว ตอนแรกฉันเพิ่มคอลัมน์เป็นโมฆะจากนั้นอัปเดตแถวทั้งหมดและในที่สุดก็เปลี่ยนคอลัมน์เป็นNOT NULLผ่านALTER TABLE ALTER COLUMNคำสั่ง สิ่งนี้ส่งผลให้เกิดธุรกรรมขนาดใหญ่สองรายการที่สร้างความน่าทึ่งให้กับ logfile (การเติบโต 170 GB)

วิธีที่เร็วที่สุดที่ฉันพบคือ:

  1. เพิ่มคอลัมน์โดยใช้ค่าเริ่มต้น

    ALTER TABLE table1 ADD column1 INT NOT NULL DEFAULT (1)
  2. ปล่อยข้อ จำกัด เริ่มต้นโดยใช้ dynamic SQL เนื่องจากข้อ จำกัด ไม่ได้ถูกตั้งชื่อมาก่อน:

    DECLARE 
        @constraint_name SYSNAME,
        @stmt NVARCHAR(510);
    
    SELECT @CONSTRAINT_NAME = DC.NAME
    FROM SYS.DEFAULT_CONSTRAINTS DC
    INNER JOIN SYS.COLUMNS C
        ON DC.PARENT_OBJECT_ID = C.OBJECT_ID
        AND DC.PARENT_COLUMN_ID = C.COLUMN_ID
    WHERE
        PARENT_OBJECT_ID = OBJECT_ID('table1')
        AND C.NAME = 'column1';
    

เวลาดำเนินการลดลงจาก> 30 นาทีเป็น 10 นาทีรวมถึงการทำซ้ำการเปลี่ยนแปลงผ่านการจำลองแบบของทรานแซคชัน ฉันใช้การติดตั้ง SQL Server 2008 (SP2)


2

ฉันวิ่งทดสอบต่อไปนี้:

create table tblCheckResult(
        ColID   int identity
    ,   dtoDateTime Datetimeoffset(0) null
    )

 go

insert into tblCheckResult (dtoDateTime)
select getdate()
go 10000

checkpoint 

ALTER TABLE tblCheckResult 
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL

select * from fn_dblog(null,null)

ฉันเชื่อว่าสิ่งนี้เกี่ยวข้องกับพื้นที่สงวนที่บันทึกไว้ในกรณีที่คุณย้อนกลับธุรกรรม ดูในฟังก์ชัน fn_dblog ที่คอลัมน์ 'Log Reserve' สำหรับแถว LOP_BEGIN_XACT และดูว่ามีพื้นที่ว่างเท่าใดในการพยายามจอง


หากคุณลองselect * FROM fn_dblog(null, null) where AllocUnitName='dbo.tblCheckResult' AND Operation = 'LOP_MODIFY_ROW'คุณสามารถดูการอัปเดต 10,000 แถว
Martin Smith

-2

พฤติกรรมนี้แตกต่างกันใน SQL Server 2012 ดูhttp://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/

จำนวนของบันทึกการทำงานที่สร้างขึ้นสำหรับ SQL Server 2008 R2 และการเปิดตัวที่ต่ำกว่าจะสูงกว่าจำนวนของบันทึกการใช้งานสำหรับ SQL Server 2012 อย่างมาก


2
คำถามคือเหตุผลที่เปลี่ยนคอลัมน์ที่มีอยู่เพื่อNOT NULLทำให้เกิดการบันทึก การเปลี่ยนแปลงในปี 2012 เกี่ยวกับการเพิ่มNOT NULLคอลัมน์ใหม่ด้วยค่าเริ่มต้น
Martin Smith
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.