Ubah cepat kolom NVARCHAR (4000) ke NVARCHAR (260)

12

Saya memiliki masalah kinerja dengan hibah memori yang sangat besar yang menangani tabel ini dengan beberapa NVARCHAR(4000)kolom. Masalahnya adalah kolom-kolom ini tidak pernah lebih besar dari NVARCHAR(260).

Menggunakan

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

hasil dalam SQL Server menulis ulang seluruh tabel (dan menggunakan ukuran tabel 2x dalam ruang log), yang miliaran baris, hanya untuk mengubah apa-apa, bukan pilihan. Menambah lebar kolom tidak memiliki masalah ini, tetapi menguranginya.

Saya telah mencoba membuat batasan CHECK (DATALENGTH([col]) <= 520)atau CHECK (LEN([col]) <= 260)dan SQL Server masih memutuskan untuk menulis ulang seluruh tabel.

Apakah ada cara untuk mengubah tipe data kolom sebagai operasi metadata saja? Tanpa biaya menulis ulang seluruh tabel? Saya menggunakan SQL Server 2017 (14.0.2027.2 dan 14.0.3192.2).

Berikut adalah contoh tabel DDL yang digunakan untuk mereproduksi:

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

Dan kemudian jalankan ALTER.

Nick Whaley
sumber

Jawaban:

15

Apakah ada cara untuk mengubah tipe data kolom sebagai operasi metadata saja?

Saya rasa tidak, inilah cara kerja produk saat ini. Ada beberapa solusi yang sangat bagus untuk batasan yang diusulkan dalam jawaban Joe ini .

... hasil dalam SQL Server menulis ulang seluruh tabel (dan menggunakan ukuran tabel 2x dalam ruang log)

Saya akan menanggapi dua bagian dari pernyataan itu secara terpisah.

Menulis Ulang Meja

Seperti yang saya sebutkan sebelumnya, sebenarnya tidak ada cara untuk menghindari ini. Itu tampaknya menjadi kenyataan dari situasi itu, bahkan jika itu tidak masuk akal sepenuhnya dari perspektif kita sebagai pelanggan.

Melihat DBCC PAGEsebelum dan sesudah mengubah kolom dari 4000 menjadi 260 menunjukkan bahwa semua data diduplikasi pada halaman data (tabel pengujian saya memiliki 'A'260 kali berturut-turut):

Cuplikan layar bagian data halaman dbcc sebelum dan sesudah

Pada titik ini, ada dua salinan data yang sama persis di halaman. Kolom "lama" pada dasarnya dihapus (id diubah dari id = 2 menjadi id = 67108865), dan versi "baru" kolom diperbarui untuk menunjuk ke offset baru dari data pada halaman:

Cuplikan layar bagian metadata kolom halaman dbcc sebelum dan sesudah

Menggunakan 2x Tabel Ukuran dalam Ruang Log

Menambahkan WITH (ONLINE = ON)ke akhir ALTERpernyataan mengurangi aktivitas logging sekitar setengahnya , jadi ini adalah satu peningkatan yang dapat Anda lakukan untuk mengurangi jumlah penulisan ke disk / ruang disk yang dibutuhkan.

Saya menggunakan test harness ini untuk mencobanya:

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

Saya memeriksa sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)sebelum dan sesudah menjalankan ALTERpernyataan, dan berikut ini perbedaannya:

Default (Offline) ALTER

  • File data ditulis / byte ditulis: 34.809 / 2.193.801.216
  • File log menulis / byte yang ditulis: 40.953 / 1.484.910.080

On line ALTER

  • File data ditulis / byte ditulis: 36.874 / 1.693.745.152 (turun 22,8%)
  • File log menulis / byte ditulis: 24.680 / 866.166.272 (41% turun)

Seperti yang Anda lihat, ada sedikit penurunan pada file data tulis, dan penurunan besar dalam file log menulis.

Josh Darnell
sumber
15

Saya tidak tahu cara untuk secara langsung mencapai apa yang Anda cari di sini. Perhatikan bahwa pengoptimal kueri saat ini tidak cukup pintar untuk memperhitungkan kendala dalam penghitungan hibah memori, jadi kendala itu tidak akan membantu. Beberapa metode yang menghindari penulisan ulang data tabel:

  1. CAST kolom sebagai NVARCHAR (260) di semua kode yang menggunakannya. Pengoptimal kueri akan menghitung hibah memori menggunakan tipe data yang dicor dan bukan yang mentah.
  2. Ganti nama tabel dan buat tampilan yang melakukan pemerannya. Ini menyelesaikan hal yang sama seperti opsi 1 tetapi dapat membatasi jumlah kode yang perlu Anda perbarui.
  3. Buat kolom terhitung yang tidak tetap dengan tipe data yang tepat dan pilih semua kueri Anda dari kolom itu alih-alih yang asli.
  4. Ganti nama kolom yang ada dan tambahkan kolom yang dihitung dengan nama asli. Kemudian sesuaikan semua kueri Anda dengan membuat pembaruan atau menyisipkan ke kolom asli untuk menggunakan nama kolom baru.
Joe Obbish
sumber
2

Saya telah mengalami situasi yang sama beberapa kali.

Langkah :

Tambahkan col baru dengan lebar yang diinginkan

Gunakan kursor, dengan beberapa ribu iterasi (mungkin sepuluh atau dua puluh ribu) per komit untuk menyalin data dari kolom lama ke kolom baru

Jatuhkan kolom lama

Ganti nama kolom baru dengan nama kolom lama

Tada!

Jonesome Reinstate Monica
sumber
3
Bagaimana jika beberapa catatan yang sudah Anda salin akhirnya diperbarui, atau dihapus?
George.Palacios
1
Sangat mudah untuk melakukan satu final update table set new_col = old_col where new_col <> old_col;sebelum menjatuhkan old_col.
Colin 't Hart
1
@ Colin'tHart pendekatan itu tidak akan bekerja dengan jutaan baris ... transaksi menjadi besar, dan itu memblokir ....
Jonesome Reinstate Monica
@smithmith Pertama, Anda melakukan apa yang Anda jelaskan di atas. Kemudian, sebelum menjatuhkan kolom asli, jika ada pembaruan untuk data asli sementara itu, jalankan pernyataan pembaruan itu. Seharusnya hanya memengaruhi beberapa baris yang telah dimodifikasi. Atau apakah saya melewatkan sesuatu?
Colin 't Hart
Untuk mencakup baris yang diperbarui selama proses, mencoba menghindari pemindaian penuh yang where new_col <> old_coltidak menghasilkan klausa pemfilteran lainnya, Anda bisa menambahkan pemicu untuk membawa perubahan ini saat terjadi dan menghapusnya di akhir proses. Masih merupakan hit kinerja potensial, tetapi banyak jumlah kecil selama proses alih-alih satu hit besar pada akhirnya, mungkin (tergantung pada pola pembaruan aplikasi Anda untuk tabel) menambahkan total jauh lebih sedikit daripada total satu hit besar .
David Spillett
1

Yah ada alternatif tergantung pada ruang yang tersedia di database Anda.

  1. Buat salinan persis meja Anda (mis. new_table), Kecuali untuk kolom tempat Anda akan menyingkat NVARCHAR(4000)menjadi 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. Di jendela pemeliharaan salin data dari tabel "rusak" ( table) ke tabel "tetap" ( new_table) dengan sederhana 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. Ganti nama tabel "broken" tablemenjadi sesuatu yang lain:

    EXEC sp_rename 'table', 'old_table';  
  4. Ganti nama tabel "tetap" new_tablemenjadi table:

    EXEC sp_rename 'new_table', 'table';  
  5. Jika semuanya baik-baik saja, letakkan tabel berganti nama "broken":

     DROP TABLE [old_table]
     GO

Ini dia.

Menjawab Pertanyaan Anda

Apakah ada cara untuk mengubah tipe data kolom sebagai operasi metadata saja?

Tidak. Saat ini tidak memungkinkan

Tanpa biaya menulis ulang seluruh tabel?

Tidak.
( Lihat solusi saya dan yang lainnya. )

John alias hot2use
sumber
"Masukkan ke dalam pilih dari" Anda akan menghasilkan, di atas meja besar (jutaan atau miliaran baris) dalam transaksi BESAR, yang dapat membuat DB berhenti selama puluhan atau ratusan menit. (Serta membuat
ldf