Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Skema Switch-A-Roo :Bagian 2

Kembali pada bulan Agustus saya menulis posting tentang metodologi pertukaran skema saya untuk T-SQL Selasa. Pendekatan ini pada dasarnya memungkinkan Anda untuk malas memuat salinan tabel (misalnya, semacam tabel pencarian) di latar belakang untuk meminimalkan gangguan dengan pengguna:setelah tabel latar belakang diperbarui, semua yang diperlukan untuk mengirimkan data yang diperbarui bagi pengguna merupakan gangguan yang cukup lama untuk melakukan perubahan metadata.

Dalam posting itu, saya menyebutkan dua peringatan bahwa metodologi yang saya perjuangkan selama bertahun-tahun saat ini tidak memenuhi:kendala kunci asing dan statistik . Ada sejumlah fitur lain yang dapat mengganggu teknik ini juga. Salah satu yang muncul dalam percakapan baru-baru ini:pemicu . Dan masih ada lagi:kolom identitas , batasan kunci utama , batasan default , periksa batasan , batasan yang mereferensikan UDF , indeks , tampilan (termasuk tampilan yang diindeks , yang memerlukan SCHEMABINDING ), dan partisi . Saya tidak akan membahas semua ini hari ini, tetapi saya pikir saya akan menguji beberapa untuk melihat apa yang sebenarnya terjadi.

Saya akan mengakui bahwa solusi asli saya pada dasarnya adalah snapshot orang miskin, tanpa semua kerepotan, seluruh database, dan persyaratan lisensi solusi seperti replikasi, mirroring, dan Grup Ketersediaan. Ini adalah salinan tabel hanya-baca dari produksi yang sedang "dicerminkan" menggunakan T-SQL dan teknik pertukaran skema. Jadi mereka tidak memerlukan kunci mewah, batasan, pemicu, dan fitur lainnya. Tetapi saya melihat bahwa teknik ini dapat berguna dalam lebih banyak skenario, dan dalam skenario tersebut beberapa faktor di atas dapat berperan.

Jadi mari kita siapkan sepasang tabel sederhana yang memiliki beberapa properti ini, lakukan pertukaran skema, dan lihat apa yang rusak. :-)

Pertama, skema:

CREATE SCHEMA prep;
GO
CREATE SCHEMA live;
GO
CREATE SCHEMA holder;
GO

Sekarang, tabel di live skema, termasuk pemicu dan UDF:

CREATE FUNCTION dbo.udf()
RETURNS INT 
AS
BEGIN
  RETURN (SELECT 20);
END
GO
 
CREATE TABLE live.t1
(
  id INT IDENTITY(1,1),
  int_column INT NOT NULL DEFAULT 1,
  udf_column INT NOT NULL DEFAULT dbo.udf(),
  computed_column AS CONVERT(INT, int_column + 1),
  CONSTRAINT pk_live PRIMARY KEY(id),
  CONSTRAINT ck_live CHECK (int_column > 0)
);
GO
 
CREATE TRIGGER live.trig_live
ON live.t1
FOR INSERT
AS
BEGIN
  PRINT 'live.trig';
END
GO

Sekarang, kita ulangi hal yang sama untuk salinan tabel di prep . Kami juga memerlukan salinan pemicu kedua, karena kami tidak dapat membuat pemicu di prep skema yang mereferensikan tabel di live , atau sebaliknya. Kami sengaja akan menetapkan identitas ke seed yang lebih tinggi dan nilai default yang berbeda untuk int_column (untuk membantu kami melacak dengan lebih baik salinan tabel mana yang benar-benar kami tangani setelah beberapa pertukaran skema):

CREATE TABLE prep.t1
(
  id INT IDENTITY(1000,1),
  int_column INT NOT NULL DEFAULT 2,
  udf_column INT NOT NULL DEFAULT dbo.udf(),
  computed_column AS CONVERT(INT, int_column + 1),
  CONSTRAINT pk_prep PRIMARY KEY(id),
  CONSTRAINT ck_prep CHECK (int_column > 1)
);
GO
 
CREATE TRIGGER prep.trig_prep
ON prep.t1
FOR INSERT
AS
BEGIN
  PRINT 'prep.trig';
END
GO

Sekarang, mari kita masukkan beberapa baris ke dalam setiap tabel dan amati hasilnya:

SET NOCOUNT ON;
 
INSERT live.t1 DEFAULT VALUES;
INSERT live.t1 DEFAULT VALUES;
 
INSERT prep.t1 DEFAULT VALUES;
INSERT prep.t1 DEFAULT VALUES;
 
SELECT * FROM live.t1;
SELECT * FROM prep.t1;

Hasil:

id int_column udf_column computed_column 1

1 20 2 2

1 20 2

Hasil dari live.t1

id int_column udf_column computed_column 1000

2 20 3 1001

2 20 3

Hasil dari prep.t1

Dan di panel pesan:

live.trig
live.trig
prep.trig
prep.trig

Sekarang, mari kita lakukan pertukaran skema sederhana:

 -- assume that you do background loading of prep.t1 here
 
BEGIN TRANSACTION;
  ALTER SCHEMA holder TRANSFER prep.t1;
  ALTER SCHEMA prep   TRANSFER live.t1;
  ALTER SCHEMA live   TRANSFER holder.t1;
COMMIT TRANSACTION;

Dan kemudian ulangi latihan ini:

SET NOCOUNT ON;
 
INSERT live.t1 DEFAULT VALUES;
INSERT live.t1 DEFAULT VALUES;
 
INSERT prep.t1 DEFAULT VALUES;
INSERT prep.t1 DEFAULT VALUES;
 
SELECT * FROM live.t1;
SELECT * FROM prep.t1;

Hasil di tabel tampak oke:

id int_column udf_column computed_column 1

1 20 2 2

1 20 2 3

1 20 2 4

1 20 2

Hasil dari live.t1

id int_column udf_column computed_column 1000

2 20 3 1001

2 20 3 1002

2 20 3 1003

2 20 3

Hasil dari prep.t1

Tetapi panel pesan mencantumkan keluaran pemicu dalam urutan yang salah:

prep.trig
prep.trig
live.trig
live.trig

Jadi, mari gali semua metadata. Berikut adalah kueri yang akan dengan cepat memeriksa semua kolom identitas, pemicu, kunci utama, default, dan memeriksa batasan untuk tabel ini, dengan fokus pada skema objek terkait, nama, dan definisi (dan nilai seed / terakhir untuk kolom identitas):

SELECT 
  [type] = 'Check', 
  [schema] = OBJECT_SCHEMA_NAME(parent_object_id), 
  name, 
  [definition]
FROM sys.check_constraints
WHERE OBJECT_SCHEMA_NAME(parent_object_id) IN (N'live',N'prep')
UNION ALL
SELECT 
  [type] = 'Default', 
  [schema] = OBJECT_SCHEMA_NAME(parent_object_id), 
  name, 
  [definition]
FROM sys.default_constraints
WHERE OBJECT_SCHEMA_NAME(parent_object_id) IN (N'live',N'prep')
UNION ALL
SELECT 
  [type] = 'Trigger',
  [schema] = OBJECT_SCHEMA_NAME(parent_id), 
  name, 
  [definition] = OBJECT_DEFINITION([object_id])
FROM sys.triggers
WHERE OBJECT_SCHEMA_NAME(parent_id) IN (N'live',N'prep')
UNION ALL
SELECT 
  [type] = 'Identity',
  [schema] = OBJECT_SCHEMA_NAME([object_id]),
  name = 'seed = ' + CONVERT(VARCHAR(12), seed_value), 
  [definition] = 'last_value = ' + CONVERT(VARCHAR(12), last_value)
FROM sys.identity_columns
WHERE OBJECT_SCHEMA_NAME([object_id]) IN (N'live',N'prep')
UNION ALL
SELECT
  [type] = 'Primary Key',
  [schema] = OBJECT_SCHEMA_NAME([parent_object_id]),
  name,
  [definition] = ''
FROM sys.key_constraints
WHERE OBJECT_SCHEMA_NAME([object_id]) IN (N'live',N'prep');

Hasil menunjukkan kekacauan metadata:

ketik skema nama definisi Cek persiapan ck_live ([int_column]>(0)) Cek langsung ck_prep ([int_column]>(1)) Bawaan persiapan df_live1 ((1)) Bawaan persiapan df_live2 ([dbo].[udf]()) Bawaan langsung df_prep1 ((2)) Bawaan langsung df_prep2 ([dbo].[udf]()) Pemicu persiapan trig_live CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN PRINT 'live.trig'; END Pemicu langsung persiapan_trig CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END Identitas persiapan biji =1 nilai_terakhir =4 Identitas langsung biji =1000 nilai_terakhir =1003 Kunci Utama persiapan pk_live Kunci Utama langsung pk_prep

Metadata bebek-bebek-angsa

Masalah dengan kolom identitas dan batasan tampaknya tidak menjadi masalah besar. Meskipun objek *tampaknya* menunjuk ke objek yang salah menurut tampilan katalog, fungsionalitas – setidaknya untuk sisipan dasar – beroperasi seperti yang Anda harapkan jika Anda belum pernah melihat metadata.

Masalah besar adalah dengan pemicu – lupa sejenak betapa sepele saya membuat contoh ini, di dunia nyata, mungkin referensi tabel dasar dengan skema dan nama. Dalam hal ini, ketika dilampirkan ke meja yang salah, semuanya bisa berjalan ... yah, salah. Mari beralih kembali:

BEGIN TRANSACTION;
  ALTER SCHEMA holder TRANSFER prep.t1;
  ALTER SCHEMA prep   TRANSFER live.t1;
  ALTER SCHEMA live   TRANSFER holder.t1;
COMMIT TRANSACTION;

(Anda dapat menjalankan kueri metadata lagi untuk meyakinkan diri sendiri bahwa semuanya kembali normal.)

Sekarang mari kita ubah pemicunya *hanya* di live versi untuk benar-benar melakukan sesuatu yang berguna (yah, "berguna" dalam konteks eksperimen ini):

ALTER TRIGGER live.trig_live
ON live.t1
FOR INSERT
AS
BEGIN
  SELECT i.id, msg = 'live.trig'
    FROM inserted AS i 
    INNER JOIN live.t1 AS t 
    ON i.id = t.id;
END
GO

Sekarang mari kita sisipkan satu baris:

INSERT live.t1 DEFAULT VALUES;

Hasil:

id    msg
----  ----------
5     live.trig

Kemudian lakukan swap lagi:

BEGIN TRANSACTION;
  ALTER SCHEMA holder TRANSFER prep.t1;
  ALTER SCHEMA prep   TRANSFER live.t1;
  ALTER SCHEMA live   TRANSFER holder.t1;
COMMIT TRANSACTION;

Dan masukkan baris lain:

INSERT live.t1 DEFAULT VALUES;

Hasil (di panel pesan):

prep.trig

Uh oh. Jika kita melakukan pertukaran skema ini sekali dalam satu jam, maka selama 12 jam setiap hari, pemicunya tidak melakukan apa yang kita harapkan, karena ini terkait dengan salinan tabel yang salah! Sekarang mari kita ubah versi pemicu "persiapan":

ALTER TRIGGER prep.trig_prep
ON prep.t1
FOR INSERT
AS
BEGIN
  SELECT i.id, msg = 'prep.trig'
    FROM inserted AS i 
	INNER JOIN prep.t1 AS t 
	ON i.id = t.id;
END
GO

Hasil:

Pesan 208, Level 16, Status 6, Prosedur trig_prep, Baris 1
Nama objek 'prep.trig_prep' tidak valid.

Yah, itu pasti tidak baik. Karena kita berada dalam fase metadata-is-swapped, tidak ada objek seperti itu; pemicunya sekarang live.trig_prep dan prep.trig_live . Bingung belum? Gerakan mengungkap kekerasan seksual demi menghapuskannya. Jadi mari kita coba ini:

EXEC sp_helptext 'live.trig_prep';

Hasil:

CREATE TRIGGER prep.trig_prep
ON prep.t1
FOR INSERT
AS
BEGIN
  PRINT 'prep.trig';
END

Nah, bukankah itu lucu? Bagaimana cara mengubah pemicu ini ketika metadatanya bahkan tidak tercermin dengan benar dalam definisinya sendiri? Mari kita coba ini:

ALTER TRIGGER live.trig_prep
ON prep.t1
FOR INSERT
AS
BEGIN
  SELECT i.id, msg = 'prep.trig'
    FROM inserted AS i 
    INNER JOIN prep.t1 AS t 
    ON i.id = t.id;
END
GO

Hasil:

Msg 2103, Level 15, Status 1, Prosedur trig_prep, Baris 1
Tidak dapat mengubah pemicu 'live.trig_prep' karena skemanya berbeda dari skema tabel atau tampilan target.

Ini juga tidak baik, jelas. Tampaknya tidak ada cara yang baik untuk menyelesaikan skenario ini yang tidak melibatkan pertukaran objek kembali ke skema aslinya. Saya dapat mengubah pemicu ini menjadi melawan live.t1 :

ALTER TRIGGER live.trig_prep
ON live.t1
FOR INSERT
AS
BEGIN
  SELECT i.id, msg = 'live.trig'
    FROM inserted AS i 
    INNER JOIN live.t1 AS t 
    ON i.id = t.id;
END
GO

Tapi sekarang saya memiliki dua pemicu yang mengatakan, dalam teks isi mereka, bahwa mereka beroperasi melawan live.t1 , tetapi hanya yang ini yang benar-benar mengeksekusi. Ya, kepala saya berputar (dan begitu juga Michael J. Swart (@MJSwart) di posting blog ini). Dan perhatikan bahwa, untuk membersihkan kekacauan ini, setelah menukar skema kembali, saya dapat menghapus pemicu dengan nama aslinya:

DROP TRIGGER live.trig_live;
DROP TRIGGER prep.trig_prep;

Jika saya mencoba DROP TRIGGER live.trig_prep; , misalnya, saya mendapatkan kesalahan objek tidak ditemukan.

Resolusi?

Solusi untuk masalah pemicu adalah membuat CREATE TRIGGER secara dinamis kode, dan jatuhkan dan buat ulang pemicu, sebagai bagian dari swap. Pertama, mari kita kembalikan trigger ke tabel *saat ini* di live (Anda dapat memutuskan dalam skenario Anda jika Anda membutuhkan pemicu di prep versi tabel sama sekali):

CREATE TRIGGER live.trig_live
ON live.t1
FOR INSERT
AS
BEGIN
  SELECT i.id, msg = 'live.trig'
    FROM inserted AS i 
    INNER JOIN live.t1 AS t 
    ON i.id = t.id;
END
GO

Sekarang, contoh cepat tentang bagaimana skema swap baru kami akan bekerja (dan Anda mungkin harus menyesuaikan ini untuk menangani setiap pemicu, jika Anda memiliki beberapa pemicu, dan ulangi untuk skema di prep versi, jika Anda perlu mempertahankan pemicu di sana juga. Berhati-hatilah agar kode di bawah ini, untuk singkatnya, mengasumsikan bahwa hanya ada *satu* pemicu di live.t1 .

BEGIN TRANSACTION;
  DECLARE 
    @sql1 NVARCHAR(MAX),
    @sql2 NVARCHAR(MAX);
 
  SELECT 
    @sql1 = N'DROP TRIGGER live.' + QUOTENAME(name) + ';',
    @sql2 = OBJECT_DEFINITION([object_id])
  FROM sys.triggers
  WHERE [parent_id] = OBJECT_ID(N'live.t1');
 
  EXEC sp_executesql @sql1; -- drop the trigger before the transfer
 
  ALTER SCHEMA holder TRANSFER prep.t1;
  ALTER SCHEMA prep   TRANSFER live.t1;
  ALTER SCHEMA live   TRANSFER holder.t1;
 
  EXEC sp_executesql @sql2; -- re-create it after the transfer
COMMIT TRANSACTION;

Solusi lain (yang kurang diinginkan) adalah melakukan seluruh operasi pertukaran skema dua kali, termasuk operasi apa pun yang terjadi terhadap prep versi tabel. Yang sebagian besar mengalahkan tujuan pertukaran skema di tempat pertama:mengurangi waktu pengguna tidak dapat mengakses tabel dan memberi mereka data yang diperbarui dengan gangguan minimal.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Python REST API Dengan Flask, Connexion, dan SQLAlchemy – Bagian 2

  2. Cara Menghitung Perbedaan Antara Dua Tanggal di T-SQL

  3. pulau khusus

  4. Kebiasaan buruk:Menghitung baris dengan cara yang sulit

  5. Menangani Pembuatan Indeks dengan MongoEngine dengan Python