ALTER COLUMN NULL DEĞİL, neden büyük günlük dosyası büyümesine neden oluyor?


56

Verileri için 4,3 GB alan disk 64m satır içeren bir tablo var.

Her satır yaklaşık 30 bayt tamsayı sütunu, artı NVARCHAR(255)metin için değişken bir sütundur.

Veri türüne sahip bir NULLABLE sütun ekledim Datetimeoffset(0).

Daha sonra her satır için bu sütunu güncelledim ve tüm yeni eklerin bu sütuna bir değer koyduğundan emin oldum.

Bir kez NULL giriş olmadığında, yeni alanımı zorunlu hale getirmek için bu komutu çalıştırdım:

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

Sonuç, işlem günlüğü büyüklüğünde BÜYÜK bir büyüme oldu - 6GB'dan 36GB'ın üzerinde alana kadar tükendi!

SQL Server 2008 R2'nin bu basit komut için bu kadar büyük bir büyümeyle sonuçlanması için ne yaptığını bilen var mı?


7
SQL Server 2012 Enterprise ,NOT NULL varsayılan olarak bir meta veri işlemi olan bir sütun ekleme özelliğini ekler . Ayrıca belgelerde "Çevrimiçi İşlem Olarak NOT NULL Sütun Ekleme" konusuna bakın .
Paul Beyaz

Yanıtlar:


48

Bir sütunu NOT NULL olarak değiştirdiğinizde , NULL değer olmasa bile , SQL Server her sayfaya dokunmak zorundadır . Doldurma faktörünüze bağlı olarak bu aslında çok fazla sayfa bölmesine yol açabilir. Dokunulan her sayfa elbette günlüğe kaydedilmek zorundadır ve birçok sayfa için iki değişikliğin günlüğe kaydedilmesi gerekeceğinden şüpheleniyorum. Her şey tek bir geçişte yapıldığı için, günlük, tüm değişiklikleri hesaba katmak zorundadır, böylece iptal düğmesine basarsanız, tam olarak ne geri alınacağını bilir.


Bir örnek. Basit masa:

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

Şimdi sayfa detaylarına bakalım. Öncelikle hangi sayfa ve DB_ID ile uğraştığımızı bulmamız gerekiyor. Benim durumumda bir veritabanı oluşturdum foove DB_ID 5 oldu.

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

Çıktı, sayfa 159 ile ilgilendiğimi belirtti ( DBCC INDçıktıdaki tek satır PageType = 1).

Şimdi, OP senaryosunda ilerlerken bazı sayfa seçimlerini inceleyelim.

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

görüntü tanımını buraya girin

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

görüntü tanımını buraya girin

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

görüntü tanımını buraya girin

Şimdi, bu konuda tüm cevapları alamadım, çünkü derin bir iç adam değilim. Ancak, hem güncelleme işlemi hem de NOT NULL kısıtlamasının eklenmesinin sayfaya inkar edilemez bir şekilde yazdığı açıktır, ikincisi bunu tamamen farklı bir şekilde yapar. Aslında, bitlerle uğraşmak yerine kaydın yapısını değiştirmek yerine, kayıt edilemez bir sütun için boşaltılabilir sütunu değiştirerek değiştirmiş gibi görünüyor. Bunu yapmak zorunda, neden emin değilim - depolama motoru ekibi için iyi bir soru sanırım. SQL Server 2012'nin bu senaryoların bazılarını çok daha iyi ele aldığına inanıyorum, FWIW - ama henüz ayrıntılı testler yapmadım.


4
Bu davranış, SQL Server'ın sonraki sürümlerinde önemli ölçüde değişti. 2016 RC2'yi kontrol ettim ve bu kesin senaryo için ve tablodaki 1 milyon satırın, sütun için tüm değerler önceden belirtilmişse NULL'dan NOT NULL'a değişim sırasında yalnızca 29 günlük kaydı oluşturulduğunu öğrendim.
Endrju

32

Komutu yerine getirirken

ALTER COLUMN ... NOT NULL

Bu, Sütun Ekle, Güncelle, Sütun Bırak işlemi olarak uygulanıyor gibi görünmektedir.

  • sys.sysrscolsYeni bir sütunu temsil etmek için yeni bir satır eklenir . statusBit 128izin vermez sütun gösteren ayarlanır NULLs
  • Tablonun her satırında yeni sütun değerini eski colum değerine ayarlayan bir güncelleme yapılır. Satırın "önce" ve "sonra" sürümleri tamamen aynıysa, bu işlem işlem günlüğüne herhangi bir şeyin yazılmasına neden olmaz, aksi takdirde güncelleme günlüğe kaydedilir.
  • Düştü gibi orijinal sütun işaretlenir (bu meta verileri yalnızca değişiklik olduğunu sys.sysrscols. rscolidBüyük bir tamsayı ve güncellendi statusdüştü belirtilen üzerine biraz 2 seti)
  • sys.sysrscolsYeni sütunun girişi rscolid, eski sütunun adını verecek şekilde değiştirilir .

Çok sayıda günlüğe kaydetme potansiyeline sahip olan işlem UPDATE, tablodaki tüm satırlardan biridir, ancak bu her zaman gerçekleşeceği anlamına gelmez. Satırdaki "önce" ve "sonra" görüntüleri aynıysa, bu güncelleme olmayan bir güncelleme olarak değerlendirilir ve testimden bugüne kadar kaydedilmez.

Bu nedenle, neden çok fazla günlük tuttuğunuza ilişkin açıklama, satırın neden "önce" ve "sonra" sürümlerinin aynı olmadığına bağlı olacaktır.

Biçimde depolanan değişken uzunluklu sütunlar için FixedVar, ayarın NOT NULLher zaman günlüğe kaydedilmesi gereken satırda bir değişikliğe neden olduğunu gördüm . Sütun sayısı ve değişken uzunluklu sütun sayısı hem artar hem de verileri kopyalayan değişken uzunluk bölümünün sonuna yeni sütun eklenir.

datetimeoffset(0)bununla birlikte sabit uzunluktur ve FixedVarformatta depolanan sabit uzunluklu sütunlar için , eski ve yeni sütunlara, satırın sabit uzunluk veri bölümünde aynı yuvaya verilmiş gibi görünmektedir ve her ikisi de "önce" ile aynı uzunluk ve değerdedir ve Satırın "sonra" sürümleri aynıdır . Bu @ Aaron'un cevabında görülebilir. Önce ve sonra sıranın iki sürümü ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;are

0x10000c00 01000000 00000000 020000

Bu günlüğe kaydedilmedi.

Mantıksal olarak, olayları açıklamamdan, sütun sayısının 02arttırılması gerektiği için aslında satırın burada farklı olması gerekir, 03ancak uygulamada böyle bir değişiklik olmaz.

Bunun sabit uzunluktaki bir sütunda neden olabileceğine ilişkin bazı olası nedenler

  • Sütun başlangıçta olduğu gibi bildirildiyse SPARSE, yeni sütun satır öncesi ve sonrası görüntülerin farklı olmasına neden olacak şekilde satırın orijinalinden farklı bir bölümünde saklanır.
  • Sıkıştırma seçeneklerinden herhangi birini kullanıyorsanız, satırın önceki ve sonraki sürümleri, CD dizisindeki sütun sayısı bölümü arttıkça farklı olacaktır.
  • Anlık görüntü yalıtım seçeneklerinden birinin etkin olduğu veritabanlarında, daha sonra her satırdaki sürüm bilgileri güncellenir (@ SQL Kiwi, bunun da burada açıklandığı gibi SI özelliği etkin olmayan veritabanlarında da olabileceğine işaret eder ).
  • ALTER TABLEYalnızca meta veriler olarak uygulanan ve henüz satıra uygulanmayan önceki bir işlem olabilir . Örneğin, yeni bir null değişken uzunluğa sahip sütun eklenmişse, o zaman bu aslında sadece bir meta veri olarak uygulanır ve bir sonraki güncellendiğinde sadece satırlara yazılır (bu son örnekte gerçekleşen yazı sadece güncellemelerdir). sütun sayısı bölümü ve satırın sonunda NULL_BITMAPbir NULL varcharsütun olarak herhangi bir yer tutmaz)

5

Aynı problemi 200.000.000 sıra olan bir masa için de gördüm. Başlangıçta null değerine sahip sütunu ekledim, sonra tüm satırları güncelledim ve en sonunda sütunu NOT NULLbir ALTER TABLE ALTER COLUMNcümle ile değiştirdim. Bu, iki büyük işlemin logfile'yi inanılmaz bir şekilde şişirmesiyle sonuçlandı (170 GB büyüme).

Bulduğum en hızlı yol şuydu:

  1. Varsayılan bir değer kullanarak sütunu ekleyin

    ALTER TABLE table1 ADD column1 INT NOT NULL DEFAULT (1)
  2. Kısıtlama daha önce adlandırılmamış olarak dinamik SQL kullanarak varsayılan kısıtlamayı bırakın:

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

İşlem süresi, değişikliklerin İşlemsel Çoğaltma yoluyla çoğaltılması da dahil olmak üzere> 30 dakikadan 10 dakikaya indirildi. Bir SQL Server 2008 kurulumu (SP2) çalıştırıyorum.


2

Aşağıdaki testi yaptım:

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)

İşlemi geri almanız durumunda, logun tuttuğu ayrılmış alanla ilgili olması gerektiğine inanıyorum. LOP_BEGIN_XACT satırının 'Günlük Rezervi' Sütunundaki fn_dblog işlevine bakın ve ne kadar yer ayırmaya çalıştığını görün.


Eğer denerseniz select * FROM fn_dblog(null, null) where AllocUnitName='dbo.tblCheckResult' AND Operation = 'LOP_MODIFY_ROW'10000 satır güncellemeleri görebilirsiniz.
Martin Smith

-2

Bunun davranışı SQL Server 2012'de farklıdır. Bkz. Http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/

SQL Server 2008 R2 ve daha düşük sürümler için oluşturulan günlük kayıtlarının sayısı, SQL Server 2012 için günlük kayıtlarının sayısından önemli ölçüde daha yüksek olacaktır.


2
Sorun, mevcut bir sütunu NOT NULLgünlüğe kaydetmeye neden olarak değiştirmenin nedenidir. 2012'deki değişiklik, NOT NULLvarsayılan olarak yeni bir sütun eklemekle ilgilidir .
Martin Smith
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.