पूरी तरह से लॉग फ़ाइल के विकास का कारण न होने के लिए ALTER COLUMN क्यों करता है?


56

मेरे पास 64 मीटर पंक्तियों वाली एक तालिका है जिसमें इसके डेटा के लिए डिस्क पर 4.3 जीबी है।

प्रत्येक पंक्ति पूर्णांक स्तंभों के लगभग 30 बाइट्स, साथ ही NVARCHAR(255)पाठ के लिए एक चर स्तंभ है।

मैंने डेटा प्रकार के साथ एक पूर्ण स्तंभ जोड़ा Datetimeoffset(0)

मैंने तब हर पंक्ति के लिए इस स्तंभ को अद्यतन किया और सुनिश्चित किया कि सभी नए आवेषण इस स्तंभ में एक मान रखते हैं।

एक बार जब कोई NULL प्रविष्टियाँ नहीं हुईं तो मैंने अपने नए क्षेत्र को अनिवार्य बनाने के लिए इस कमांड को चलाया:

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

इसका परिणाम लेनदेन लॉग आकार में एक बड़ी वृद्धि थी - 6GB से 36GB से अधिक जब तक यह अंतरिक्ष से बाहर भाग गया!

क्या किसी को भी यह पता नहीं है कि पृथ्वी पर SQL Server 2008 R2 इस सरल कमांड के लिए इतनी बड़ी वृद्धि का परिणाम क्या कर रहा है?


7
SQL सर्वर 2012 एंटरप्राइज़ मेटाडेटा कार्रवाई के रूप में एक डिफ़ॉल्ट के साथ एक कॉलम जोड़ने की क्षमता जोड़ता हैNOT NULL । इसके अलावा में "एक ऑनलाइन ऑपरेशन के रूप में नहीं NULL कॉलम जोड़ना" को देखने के प्रलेखन
पॉल व्हाइट

जवाबों:


48

जब आप NULL को कॉलम नहीं बदलते हैं, तो SQL सर्वर को हर एक पृष्ठ को स्पर्श करना होता है , भले ही 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) में दिलचस्पी थी ।

अब, हम ओपी के परिदृश्य के माध्यम से कदम बढ़ाते हुए कुछ चुनिंदा पृष्ठ विवरण देखते हैं।

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

यहाँ छवि विवरण दर्ज करें

अब, मेरे पास इसके सभी उत्तर नहीं हैं, क्योंकि मैं एक गहन प्रशिक्षु नहीं हूं। लेकिन यह स्पष्ट है कि - जबकि अपडेट ऑपरेशन और नॉट नाल बाधा के अलावा दोनों पृष्ठ पर नहीं लिखते हैं - बाद वाला पूरी तरह से अलग तरीके से ऐसा करता है। यह वास्तव में रिकॉर्ड की संरचना को बदलने के लिए लगता है, न कि केवल बिट्स के साथ बेला के बजाय, एक अशक्त स्तंभ के लिए अशक्त स्तंभ को स्वैप करके। ऐसा क्यों करना है, मुझे यकीन नहीं है - भंडारण इंजन टीम के लिए एक अच्छा सवाल है , मुझे लगता है। मुझे विश्वास है कि SQL Server 2012 इन परिदृश्यों में से कुछ को बेहतर तरीके से संभालता है, FWIW - लेकिन मुझे अभी तक कोई भी विस्तृत परीक्षण नहीं करना है।


4
SQL सर्वर के बाद के संस्करणों में यह व्यवहार काफी बदल गया। मैंने 2016 RC2 की जाँच की है और पाया कि इस सटीक परिदृश्य और तालिका में 1 मिलियन पंक्तियों के लिए केवल 29 लॉग रिकॉर्ड NULL से NOT NULL से परिवर्तन के दौरान उत्पन्न होते हैं यदि सभी मान स्तंभ के लिए पहले से निर्दिष्ट थे।
एंडरजू

32

जब कमांड को पूरा करते हैं

ALTER COLUMN ... NOT NULL

यह ऐड कॉलम, अपडेट, ड्रॉप कॉलम ऑपरेशन के रूप में लागू किया गया लगता है।

  • एक नया sys.sysrscolsकॉलम एक नया कॉलम प्रस्तुत करने के लिए डाला गया है। statusके लिए थोड़ा और 128स्तंभ का संकेत की अनुमति नहीं है सेट कर दिया जाता NULLरों
  • पुराने स्तंभ के नए स्तंभ मान को सेट करने वाली तालिका की प्रत्येक पंक्ति पर एक अपडेट किया जाता है। यदि पंक्ति के "पहले" और "बाद" संस्करण समान हैं तो इससे लेन-देन लॉग में किसी भी चीज को लिखे जाने का कारण नहीं बनता है अन्यथा अपडेट लॉग होता है।
  • मूल स्तंभ को चिह्नित के रूप में चिह्नित किया गया है (यह केवल मेटाडेटा में परिवर्तन है sys.sysrscolsrscolidएक बड़े पूर्णांक में अद्यतन किया जाता है और statusबिट 2 संकेतित पर सेट होता है)
  • sys.sysrscolsनए कॉलम के लिए प्रविष्टि को पुराने कॉलम के रूप में बदल दिया जाता है rscolid

जिस ऑपरेशन में बहुत सारे लॉगिंग पैदा करने की क्षमता है, UPDATEवह तालिका में सभी पंक्तियों का है, हालांकि इसका मतलब यह नहीं है कि यह हमेशा होगा । यदि पंक्ति के "पहले" और "बाद में" चित्र समान हैं तो इसे एक गैर अद्यतन अपडेट माना जाएगा और अब तक मेरे परीक्षण से लॉग इन नहीं किया जाएगा।

तो इस बात की व्याख्या कि आप बहुत सारे लॉगिंग क्यों कर रहे हैं, इस पर निर्भर करेगा कि पंक्ति के "पहले" और "बाद" क्यों समान नहीं हैं।

FixedVarप्रारूप में संग्रहीत चर लंबाई कॉलम के लिए मैंने पाया कि सेटिंग को NOT NULLहमेशा उस पंक्ति में बदलाव का कारण बनता है जिसे लॉग करने की आवश्यकता होती है। कॉलम की संख्या और चर लंबाई के कॉलम की गणना दोनों बढ़े हुए हैं और नया कॉलम डेटा की नकल करने वाली चर लंबाई अनुभाग के अंत में जोड़ा जाता है।

datetimeoffset(0)हालाँकि निश्चित लंबाई है और निश्चित लंबाई के कॉलम के लिए FixedVarप्रारूप में संग्रहीत पुराने और नए कॉलम दोनों को पंक्ति के निश्चित लंबाई डेटा भाग में एक ही स्लॉट दिया जाता है और जैसा कि वे दोनों एक ही लंबाई और "पहले" मान देते हैं । "के बाद" पंक्ति के संस्करण समान हैं । यह @ आरोन के उत्तर में देखा जा सकता है। पहले और बाद की पंक्ति के दोनों संस्करण ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;हैं

0x10000c00 01000000 00000000 020000

यह लॉग नहीं है।

तार्किक रूप से घटनाओं के मेरे विवरण से पंक्ति वास्तव में यहां अलग 02होनी चाहिए क्योंकि स्तंभ की संख्या को बढ़ाया जाना चाहिए, 03लेकिन वास्तव में ऐसा कोई परिवर्तन व्यवहार में नहीं होता है।

एक निश्चित लंबाई वाले कॉलम में ऐसा होने के कुछ संभावित कारण हो सकते हैं

  • यदि स्तंभ को मूल रूप से घोषित किया गया था, SPARSEतो नया कॉलम मूल से पंक्ति के एक अलग भाग में संग्रहीत किया जाएगा, जिससे पहले और बाद की छवियों को अलग किया जा सके।
  • यदि आप किसी भी संपीड़न विकल्प का उपयोग कर रहे हैं तो पंक्ति के पहले और बाद के संस्करण अलग-अलग होंगे क्योंकि CD सरणी में स्तंभ गणना अनुभाग बढ़े हुए हैं।
  • सक्षम किए गए स्नैपशॉट अलगाव विकल्पों में से एक के साथ डेटाबेस पर फिर प्रत्येक पंक्ति में संस्करण जानकारी अपडेट की जाती है (@SQL कीवी बताते हैं कि यह डेटाबेस में एसआई सक्षम के बिना भी हो सकता है जैसा कि यहां वर्णित है )।
  • पिछले कुछ ALTER TABLEऑपरेशन हो सकते हैं जिन्हें केवल मेटाडेटा के रूप में लागू किया गया था और इसे अभी तक पंक्ति में लागू नहीं किया गया है। उदाहरण के लिए यदि एक नया अशक्त चर लंबाई स्तंभ जोड़ा गया था, तो इसे मूल रूप से केवल मेटाडेटा के रूप में लागू किया जाता है और यह केवल पंक्तियों के लिए लिखा जाता है जब वे अगले अपडेट होते हैं (इस अंतिम उदाहरण में वास्तव में होने वाला लेखन सिर्फ अपडेट होता है स्तंभ गणना अनुभाग और पंक्ति के अंत NULL_BITMAPमें एक NULL varcharस्तंभ के रूप में कोई स्थान नहीं लेता है)

5

मुझे 200.000.000 पंक्तियों वाली तालिका के संबंध में समान समस्या का सामना करना पड़ा। प्रारंभ में मैंने कॉलम को अशक्त किया, फिर सभी पंक्तियों को अपडेट किया, और अंत में NOT NULLएक ALTER TABLE ALTER COLUMNबयान के माध्यम से कॉलम को बदल दिया । यह दो विशाल लेनदेन के परिणामस्वरूप लॉगफाइल को अविश्वसनीय रूप से उड़ा देता है (170 जीबी विकास)।

सबसे तेज़ तरीका जो मैंने पाया वह था:

  1. डिफ़ॉल्ट मान का उपयोग करके कॉलम जोड़ें

    ALTER TABLE table1 ADD column1 INT NOT NULL DEFAULT (1)
  2. डायनामिक एसक्यूएल का उपयोग करके डिफ़ॉल्ट बाधा को हटा दें क्योंकि बाधा का नाम पहले नहीं दिया गया है:

    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)

मेरा मानना ​​है कि यह आपके द्वारा लेन-देन को रोलबैक करने के मामले में आरक्षित स्थान के साथ करना है। LOP_BEGIN_XACT पंक्ति के 'लॉग रिजर्व' कॉलम में fn_dblog फ़ंक्शन में देखें और देखें कि वह कितनी जगह आरक्षित करने का प्रयास कर रहा है।


यदि आप कोशिश select * FROM fn_dblog(null, null) where AllocUnitName='dbo.tblCheckResult' AND Operation = 'LOP_MODIFY_ROW'करते हैं तो आप 10000 पंक्ति अपडेट देख सकते हैं।
मार्टिन स्मिथ

-2

SQL सर्वर 2012 में इसके लिए व्यवहार भिन्न है। http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/ देखें

SQL Server 2008 R2 और नीचे रिलीज़ के लिए जनरेट किए गए लॉग रिकॉर्ड की संख्या SQL सर्वर 2012 के लिए लॉग रिकॉर्ड की संख्या से काफी अधिक होगी।


2
सवाल यह है कि NOT NULLलॉगिंग के कारण मौजूदा कॉलम में फेरबदल क्यों किया जाए । 2012 में बदलाव NOT NULLएक डिफ़ॉल्ट के साथ एक नया कॉलम जोड़ने के बारे में है ।
मार्टिन स्मिथ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.