NVARCHAR (4000) ila NVARCHAR (260) arasındaki hızlı değiştirme sütunu


12

Birkaç NVARCHAR(4000)sütun ile bu tabloyu işleme çok büyük bellek hibeleri ile bir performans sorunu var . Şey, bu sütunlar asla daha büyük değildir NVARCHAR(260).

kullanma

ALTER TABLE [table] ALTER COLUMN [col] NVARCHAR(260) NULL

SQL Server'ın tüm tabloyu yeniden yazmasıyla sonuçlanır (ve günlük alanında 2x tablo boyutu kullanarak), milyarlarca satır olan yalnızca hiçbir şeyi değiştirmek için bir seçenek değildir. Sütun genişliğinin artırılmasında bu sorun olmaz, ancak azaltılır.

Bir kısıtlama oluşturmayı denedim CHECK (DATALENGTH([col]) <= 520)veya CHECK (LEN([col]) <= 260)SQL Server hala tüm tabloyu yeniden yazmaya karar verdi.

Sütun veri türünü salt meta veri işlemi olarak değiştirmenin bir yolu var mı? Tüm tabloyu yeniden yazma masrafı olmadan? SQL Server 2017 kullanıyorum (14.0.2027.2 ve 14.0.3192.2).

Çoğaltmak için kullanılacak örnek bir DDL tablosu:

CREATE TABLE [table](
    id INT IDENTITY(1,1) NOT NULL,
    [col] NVARCHAR(4000) NULL,
    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

Ve sonra çalıştırın ALTER.

Yanıtlar:


16

Burada aradığınızı doğrudan başarmanın bir yolunu bilmiyorum. Sorgu optimize edicinin şu anda bellek verme hesaplamaları için kısıtlamaları hesaba katacak kadar akıllı olmadığını unutmayın, bu nedenle kısıtlama yine de yardımcı olmazdı. Tablonun verilerinin yeniden yazılmasını engelleyen birkaç yöntem:

  1. Sütunu, kullanan tüm kodlarda NVARCHAR (260) olarak CAST. Sorgu iyileştirici, ham veri yerine döküm veri türünü kullanarak bellek hibesini hesaplar.
  2. Tabloyu yeniden adlandırın ve bunun yerine yayın yapan bir görünüm oluşturun. Bu seçenek 1 ile aynı şeyi yapar, ancak güncellemeniz gereken kod miktarını sınırlayabilir.
  3. Doğru veri türüne sahip kalıcı olmayan bir hesaplanmış sütun oluşturun ve tüm sorgularınızın orijinal sütun yerine bu sütundan seçim yapmasını sağlayın.
  4. Mevcut sütunu yeniden adlandırın ve orijinal adla hesaplanan sütunu ekleyin. Ardından, yeni sütun adını kullanmak için orijinal sütuna güncelleme veya ekleme yapan tüm sorgularınızı ayarlayın.

15

Sütun veri türünü salt meta veri işlemi olarak değiştirmenin bir yolu var mı?

Ben öyle düşünmüyorum, ürün şu anda böyle çalışıyor. Joe'nun cevabında önerilen bu sınırlama için gerçekten harika bazı çözümler var .

... SQL Server'ın tüm tabloyu yeniden yazmasına neden olur (ve günlük alanında 2x tablo boyutu kullanılır)

Bu ifadenin iki kısmına ayrı ayrı cevap vereceğim.

Tabloyu Yeniden Yazma

Daha önce de belirttiğim gibi, bundan kaçınmanın hiçbir yolu yoktur. Bu, müşteriler olarak bizim açımızdan tam anlam ifade etmese bile, durumun gerçekliği gibi görünüyor.

DBCC PAGESütunu 4000'den 260'a değiştirmeden önce ve sonra bakıldığında, tüm verilerin veri sayfasında çoğaltıldığını gösterir (test 'A'masamın satırında 260 kez vardı ):

Dbcc sayfasının veri bölümünün öncesi ve sonrası ekran görüntüsü

Bu noktada, sayfada aynı verilerin iki kopyası bulunur. "Eski" sütun esasen silinir (id, id = 2 yerine id = 67108865 olarak değiştirilir) ve sütunun "yeni" sürümü, sayfadaki verilerin yeni ofsetini gösterecek şekilde güncellenir:

Dbcc sayfasının sütun meta veri bölümlerinin ekran görüntüsü

Günlük Alanında 2x Tablo Boyutunu Kullanma

Ekleme WITH (ONLINE = ON)sonuna ALTERaçıklamada yaklaşık yarı yarıya günlük aktivitesini azaltan bu size gereken bir disk / disk alanı için yazma miktarını azaltmak için yapabilir bir gelişme yani.

Bu test kayışını denemek için kullandım:

USE [master];
GO
DROP DATABASE IF EXISTS [248749];
GO
CREATE DATABASE [248749] 
ON PRIMARY 
(
    NAME = N'248749', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749.mdf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
)
LOG ON 
(
    NAME = N'248749_log', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749_log.ldf', 
    SIZE = 2048000KB, 
    FILEGROWTH = 65536KB
);
GO
USE [248749];
GO

CREATE TABLE dbo.[table]
(
    id int IDENTITY(1,1) NOT NULL,
    [col] nvarchar (4000) NULL,

    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

INSERT INTO dbo.[table]
SELECT TOP (1000000)
    REPLICATE(N'A', 260)
FROM master.dbo.spt_values v1
    CROSS JOIN master.dbo.spt_values v2
    CROSS JOIN master.dbo.spt_values v3;
GO

İfadeyi sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)çalıştırmadan önce ve sonra kontrol ettim ALTERve işte farklar:

Varsayılan (Çevrimdışı) ALTER

  • Veri dosyası yazar / yazılır bayt: 34.809 / 2.193.801.216
  • Günlük dosyası yazıyor / yazılan bayt: 40.953 / 1.484.910.080

İnternet üzerinden ALTER

  • Veri dosyası yazar / yazılır bayt: 36.874 / 1.693.745.152 (% 22.8 düşüş)
  • Günlük dosyası yazıyor / yazılan bayt: 24.680 / 866.166.272 (% 41 düşüş)

Gördüğünüz gibi, veri dosyasında hafif bir düşüş vardı ve günlük dosyasında büyük bir düşüş var.


2

Birçok kez benzer bir durumdaydım.

Adımlar:

İstenen genişlikte yeni bir sütun ekleyin

Verileri eski sütundan yeni sütuna kopyalamak için taahhüt başına birkaç bin yineleme (belki de on veya yirmi bin) içeren bir imleç kullanın

Eski sütunu bırak

Yeni sütunu eski sütunun adıyla yeniden adlandır

Tada!


3
Daha önce kopyaladığınız bazı kayıtlar güncellenir veya silinirse ne olur?
George.Palacios

1
Düşmeden update table set new_col = old_col where new_col <> old_col;önce bir final yapmak çok kolay old_col.
Colin 't Hart

1
@ Colin'tHart bu yaklaşım milyonlarca satır ile çalışmaz ... işlem büyük olur ve engeller ....
Jonesome Reinstate Monica

@samsmith Önce yukarıda açıkladığınız şeyi yapın. Ardından, orijinal sütunu bırakmadan önce, orijinal verilerde herhangi bir güncelleme yapılmışsa, bu güncelleme deyimini çalıştırın. Yalnızca değiştirilmiş birkaç satırı etkilemelidir. Yoksa bir şey mi kaçırıyorum?
Colin 't Hart

İşlem sırasında güncellenen satırları kapsamak için, where new_col <> old_colbaşka hiçbir filtreleme yan tümcesiyle sonuçlanmayacak olan tam taramadan kaçınmaya çalışırken, bu değişiklikleri gerçekleştikçe taşımak ve işlemin sonunda kaldırmak için bir tetikleyici ekleyebilirsiniz. Hala potansiyel bir performans isabeti, ancak sonunda büyük bir isabet yerine sürecin uzunluğu boyunca birçok küçük miktar, muhtemelen (uygulamanızın tablo için güncelleme modeline bağlı olarak) toplamda bu büyük isabetten çok daha az ekleniyor .
David Spillett

1

Veritabanınızdaki kullanılabilir alana bağlı olarak bir alternatif var.

  1. Masanızdaki (örneğin tam bir kopyasını oluşturma new_tablesizden kısaltılması edilecektir sütun için hariç) NVARCHAR(4000)için NVARCHAR(260):

    CREATE TABLE [new_table](
        id INT IDENTITY(1,1) NOT NULL,
        [col] NVARCHAR(260) NULL,
        CONSTRAINT [PK_test_new] PRIMARY KEY CLUSTERED (id ASC)
    );
  2. Bir bakım penceresinde "kırık" tablodan ( table) "sabit" tabloya ( new_table) basit bir şekilde verileri kopyalayın INSERT ... INTO ... SELECT ....:

    SET IDENTITY_INSERT [new_table] ON
    GO
    INSERT id, col INTO [new_table] SELECT id, col from [table]
    GO
    SET IDENTITY_INSERT [new_table] OFF
    GO
  3. "Kırık" tabloyu tablebaşka bir adla yeniden adlandırın :

    EXEC sp_rename 'table', 'old_table';  
  4. "Sabit" tabloyu şu new_tableşekilde yeniden adlandırın table:

    EXEC sp_rename 'new_table', 'table';  
  5. Her şey yolundaysa, "kırık" yeniden adlandırılmış tabloyu bırakın:

     DROP TABLE [old_table]
     GO

İşte böyle.

Sorularınızı Yanıtlama

Sütun veri türünü salt meta veri işlemi olarak değiştirmenin bir yolu var mı?

Hayır. Şu anda mümkün değil

Tüm tabloyu yeniden yazma masrafı olmadan?

Hayır.
( Çözümüme ve diğerlerine bakın. )


"Seçim için ekleme", ENORMOUS işleminde büyük bir tabloda (milyonlarca veya milyarlarca satır) DB'yi onlarca veya yüzlerce dakika boyunca durdurabilir. (Kullanımdaysa, ldf'yi muazzam ve muhtemelen kırma günlük nakliyesinin yanı sıra)
Jonesome Reinstate Monica
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.