[ Bagian 1 | Bagian 2 | Bagian 3 | Bagian 4 ]
Sejauh ini dalam seri ini, saya telah mendemonstrasikan dampak fisik langsung ke halaman saat meningkatkan ukuran dari int
ke bigint
, dan kemudian diulang melalui beberapa pemblokir umum untuk operasi ini. Dalam postingan ini, saya ingin memeriksa dua solusi potensial:satu sederhana, dan satu sangat berbelit-belit.
Cara Mudah
Saya dirampok dari guntur saya sedikit dalam komentar di posting saya sebelumnya – Keith Monroe menyarankan agar Anda hanya mengatur ulang tabel ke negatif yang lebih rendah terikat dari tipe data integer, menggandakan kapasitas Anda untuk nilai-nilai baru. Anda dapat melakukannya dengan DBCC CHECKIDENT
:
DBCC CHECKIDENT(N'dbo.TableName', RESEED, -2147483648);
Ini bisa berhasil, dengan asumsi nilai pengganti tidak memiliki arti bagi pengguna akhir (atau, jika ya, pengguna tidak akan ketakutan dengan tiba-tiba mendapatkan angka negatif). Saya kira Anda bisa menipu mereka dengan pandangan:
CREATE VIEW dbo.ViewNameAS SELECT ID =CONVERT(bigint, CASE WHEN ID <0 THEN (2147483648*2) - 1 + CONVERT(bigint, ID) ELSE ID END) FROM dbo.TableName;
Artinya pengguna yang menambahkan ID = -2147483648
akan benar-benar melihat +2147483648
, pengguna yang menambahkan ID = -2147483647
akan melihat +2147483649
, dan seterusnya. Anda harus menyesuaikan kode lain untuk memastikan membuat perhitungan terbalik ketika pengguna memasukkan ID
itu , misalnya
ALTER PROCEDURE dbo.GetRowByID @ID bigintASBEGIN SET NOCOUNT ON; MENYATAKAN @RealID bigint; SET @RealID =KASUS KETIKA @ID> 2147483647 MAKA @ID - (2147483648*2) + 1 LAIN @ID AKHIR; PILIH ID, @ID /*, kolom lain */ FROM dbo.TableName WHERE ID =@RealID;ENDGO
Saya tidak tergila-gila dengan kebingungan ini. Sama sekali. Ini berantakan, menyesatkan, dan rawan kesalahan. Dan itu mendorong visibilitas ke kunci pengganti – umumnya, IDENTITY
nilai tidak boleh diekspos ke pengguna akhir, jadi mereka seharusnya tidak peduli jika mereka adalah pelanggan 24, 642, -376, atau angka yang jauh lebih besar di kedua sisi nol.
"Solusi" ini juga mengasumsikan bahwa Anda tidak memiliki kode di mana pun yang dipesan dengan IDENTITY
kolom untuk menampilkan baris yang terakhir disisipkan terlebih dahulu, atau menyimpulkan bahwa IDENTITY
tertinggi nilai harus baris terbaru. Kode yang bisa mengandalkan urutan pengurutan IDENTITY
kolom, baik secara eksplisit maupun implisit (yang mungkin lebih dari yang Anda pikirkan jika itu adalah indeks berkerumun), tidak akan lagi menampilkan baris dalam urutan yang diharapkan – ini akan menampilkan semua baris yang dibuat setelah RESEED
, dimulai dengan yang pertama, dan kemudian akan menampilkan semua baris yang dibuat sebelum RESEED
, dimulai dari yang pertama.
Manfaat utama dari pendekatan ini adalah Anda tidak perlu mengubah tipe data, dan sebagai hasilnya, RESEED
perubahan tidak memerlukan perubahan apa pun pada indeks, batasan, atau kunci asing masuk.
Kelemahannya – selain perubahan kode yang disebutkan di atas, tentu saja – ini hanya memberi Anda waktu dalam jangka pendek. Akhirnya Anda akan menghabiskan semua bilangan bulat negatif yang tersedia juga. Dan jangan berpikir ini menggandakan masa manfaat dari versi tabel saat ini dalam hal waktu – dalam banyak kasus, pertumbuhan data semakin cepat, tidak konstan, jadi Anda akan menggunakan 2 miliar baris berikutnya jauh lebih cepat daripada 2 miliar pertama.
Jalan yang Lebih Sulit
Pendekatan lain yang dapat Anda ambil adalah berhenti menggunakan IDENTITY
kolom sama sekali; sebagai gantinya Anda dapat mengonversi menggunakan SEQUENCE
. Anda dapat membuat bigint
baru kolom, atur default ke nilai berikutnya dari SEQUENCE
, perbarui semua nilai tersebut dengan nilai dari kolom asli (dalam batch jika perlu), jatuhkan kolom asli, dan ganti nama kolom baru. Mari kita buat tabel fiktif ini dan masukkan satu baris:
CREATE TABLE dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID));GO INSERT dbo.SequenceDemo(x) VALUES('x');Selanjutnya, kita akan membuat
SEQUENCE
yang dimulai tepat di luar batas atas int:BUAT URUTAN dbo.BeyondIntAS bigintMULAI DENGAN PENINGKATAN 2147483648 SEPERTI 1;Selanjutnya, perubahan tabel yang diperlukan untuk beralih menggunakan
SEQUENCE
untuk kolom baru:MULAI TRANSAKSI; -- tambahkan kolom "identitas" baru:ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO -- atur kolom baru sama dengan nilai identitas yang ada-- untuk tabel besar, mungkin perlu melakukan ini dalam batch:UPDATE dbo.SequenceDemo SET ID2 =ID; -- sekarang buat itu tidak dapat dibatalkan dan tambahkan default dari SEQUENCE:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NILAI BERIKUTNYA UNTUK dbo.BeyondInt FOR ID2; -- perlu menghapus PK yang ada (dan indeks apa pun):ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; -- jatuhkan kolom lama dan ganti nama yang baru:ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN'; -- sekarang pasang kembali PK:ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID); TRANSAKSI KOMIT;Dalam hal ini, penyisipan berikutnya akan menghasilkan hasil berikut (perhatikan bahwa
SCOPE_IDENTITY()
tidak lagi mengembalikan nilai yang valid):INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* hasil Si----NULL ID x---------- -1 x2147483648 y */Jika tabelnya besar dan Anda perlu memperbarui kolom baru dalam batch alih-alih transaksi satu kali di atas, seperti yang telah saya jelaskan di sini – memungkinkan pengguna untuk berinteraksi dengan tabel sementara itu – Anda harus memiliki pemicu di tempat untuk mengganti
SEQUENCE
nilai untuk setiap baris baru yang dimasukkan, sehingga mereka terus mencocokkan apa yang dihasilkan dengan kode panggilan apa pun. (Ini juga mengasumsikan bahwa Anda masih memiliki beberapa ruang dalam rentang bilangan bulat untuk terus menerima beberapa pembaruan; jika tidak, jika Anda telah kehabisan rentang, Anda harus mengambil waktu henti – atau gunakan solusi mudah di atas dalam jangka pendek .)Mari kita tinggalkan semuanya dan mulai dari awal, lalu tambahkan saja kolom baru:
DROP TABLE dbo.SequenceDemo;DROP SEQUENCE dbo.BeyondInt;GO CREATE TABLE dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID));GO INSERT dbo .SequenceDemo(x) VALUES('x');GO CREATE SEQUENCE dbo.BeyondIntAS bigintMULAI DENGAN 2147483648 INCREMENT BY 1;GO ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GODan inilah pemicu yang akan kami tambahkan:
CREATE TRIGGER dbo.After_SequenceDemoON dbo.SequenceDemoAFTER INSERTASBEGIN UPDATE sd SET sd.ID2 =sd.ID FROM dbo.SequenceDemo AS sd INNER JOIN dimasukkan AS i ON sd.ID =i.ID;ENDKali ini, penyisipan berikutnya akan terus menghasilkan baris dalam kisaran bilangan bulat yang lebih rendah untuk kedua kolom, hingga semua nilai yang sudah ada sebelumnya telah diperbarui dan perubahan lainnya telah dilakukan:
INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, ID2, x FROM dbo.SequenceDemo; /* hasil Si----2 ID ID2 x---- ---- --1 NULL x2 2 y */Sekarang, kami dapat terus memperbarui
ID2
yang ada nilai sementara baris baru terus dimasukkan dalam kisaran yang lebih rendah:ATUR NOCOUNT AKTIF; MENYATAKAN @r INT =1; SAAT @r> 0MULAI TRANSAKSI; UPDATE TOP (10000) dbo.SequenceDemo SET ID2 =ID WHERE ID2 IS NULL; SET @r =@@ROWCOUNT; KOMITMEN TRANSAKSI; - TITIK PERIKSA; -- jika sederhana -- LOG CADANGAN ... -- jika penuhENDSetelah kami memperbarui semua baris yang ada, kami dapat melanjutkan dengan perubahan lainnya, lalu melepaskan pemicunya:
BEGIN TRANSACTION;ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NILAI BERIKUTNYA UNTUK dbo.BeyondInt FOR ID2;SequenceDemo DROP_SEquence;SequenceTABLE DROP_Sequence DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN';ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID);DROP TRIGGER COM_Sequence.Demo sebagai gantinya pra>Sekarang, sisipan berikutnya akan menghasilkan nilai-nilai ini:
INSERT dbo.SequenceDemo(x) VALUES('z');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* hasil Si----NULL ID x---------- -1 x2 y2147483648 z */Jika Anda memiliki kode yang bergantung pada
SCOPE_IDENTITY()
,@@IDENTITY
, atauIDENT_CURRENT()
, itu juga harus berubah, karena nilai-nilai itu tidak lagi diisi setelah penyisipan – meskipunOUTPUT
klausa harus terus bekerja dengan benar di sebagian besar skenario. Jika Anda memerlukan kode untuk terus meyakini bahwa tabel menghasilkanIDENTITY
nilai, maka Anda dapat menggunakan pemicu untuk memalsukan ini – namun itu hanya dapat mengisi@@IDENTITY
saat disisipkan, bukanSCOPE_IDENTITY()
. Ini mungkin masih memerlukan perubahan, karena dalam banyak kasus, Anda tidak ingin bergantung pada@@IDENTITY
untuk apa pun (jadi, jika Anda akan membuat perubahan, hapus semua asumsi tentangIDENTITY
kolom sama sekali).BUAT PEMICU dbo.FakeIdentityON dbo.SequenceDemoBUKAN INSERTASBEGIN SET NOCOUNT ON; DECLARE @lowestID bigint =(PILIH MIN(id) DARI dimasukkan); DECLARE @sql nvarchar(max) =N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32), @lowestID) + N',1));'; PILIH @sql +=N'INSERT @foo NILAI DEFAULT;' DARI dimasukkan; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID, x) SELECT ID, x FROM dimasukkan;ENDSekarang, sisipan berikutnya akan menghasilkan nilai-nilai ini:
INSERT dbo.SequenceDemo(x) VALUES('a');SELECT Si =SCOPE_IDENTITY(), Ident =@@IDENTITY;SELECT ID, x FROM dbo.SequenceDemo; /* hasil Si Ident----------NULL 2147483649 ID x---------- -1 x2 y2147483648 z2147483649 a */Dengan solusi ini, Anda masih harus berurusan dengan batasan, indeks, dan tabel lain dengan kunci asing masuk. Batasan dan indeks lokal cukup mudah, tetapi saya akan menangani situasi yang lebih kompleks dengan kunci asing di bagian selanjutnya dari seri ini.
Yang Tidak Berfungsi, Tapi Saya Berharap Bisa
ALTER TABLE SWITCH
bisa menjadi cara yang sangat ampuh untuk membuat beberapa perubahan metadata yang sulit dilakukan sebaliknya. Dan bertentangan dengan kepercayaan populer, ini tidak hanya melibatkan partisi, dan tidak terbatas pada Edisi Perusahaan. Kode berikut akan berfungsi di Express, dan merupakan metode yang digunakan orang untuk menambah atau menghapusIDENTITY
properti di atas meja (sekali lagi, tidak memperhitungkan kunci asing dan semua pemblokir sial lainnya).BUAT TABEL dbo.WithIdentity( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.WithoutIdentity( ID int NOT NULL); ALTER TABLE dbo.WithIdentity SWITCH TO dbo.WithoutIdentity;GO DROP TABLE dbo.WithIdentity;EXEC sys.sp_rename N'dbo.WithoutIdentity', N'dbo.WithIdentity', 'OBJECT';Ini berfungsi karena tipe data dan nullability sama persis, dan tidak ada perhatian yang diberikan pada
IDENTITY
atribut. Namun, cobalah untuk mencampur tipe data, dan semuanya tidak berjalan dengan baik:BUAT TABEL dbo.SourceTable( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.TrySwitch( ID bigint IDENTITY(1,1) NOT NULL); ALTER TABLE dbo.SourceTable BERALIH KE dbo.TrySwitch;Ini menghasilkan:
Msg 4944, Level 16, State 1
Pernyataan ALTER TABLE SWITCH gagal karena kolom 'ID' memiliki tipe data int pada tabel sumber 'dbo.SourceTable' yang berbeda dengan tipe bigint pada tabel target 'dbo.TrySwitch'.Akan luar biasa jika
SWITCH
operasi dapat digunakan dalam skenario seperti ini, di mana satu-satunya perbedaan dalam skema sebenarnya tidak *membutuhkan* perubahan fisik apa pun untuk mengakomodasi (sekali lagi, seperti yang saya tunjukkan di bagian 1, data ditulis ulang ke halaman baru, meskipun tidak perlu melakukannya).Kesimpulan
Pos ini menyelidiki dua solusi potensial untuk memberi Anda waktu sebelum mengubah
IDENTITY
Anda yang ada kolom, atau mengabaikanIDENTITY
sama sekali sekarang mendukungSEQUENCE
. Jika tidak satu pun dari solusi ini dapat Anda terima, harap perhatikan bagian 4, di mana kami akan menangani masalah ini secara langsung.—
[ Bagian 1 | Bagian 2 | Bagian 3 | Bagian 4 ]