T-SQL Tuesday bulan ini dipandu oleh Mike Fal (blog | twitter), dan topiknya adalah Trick Shots, di mana kami diundang untuk memberi tahu komunitas tentang beberapa solusi yang kami gunakan di SQL Server yang terasa, setidaknya bagi kami, sebagai semacam "tembakan trik" – sesuatu yang mirip dengan penggunaan massé, "Inggris" atau tembakan bank yang rumit dalam biliar atau snooker. Setelah bekerja dengan SQL Server selama sekitar 15 tahun, saya memiliki kesempatan untuk menemukan trik untuk memecahkan beberapa masalah yang cukup menarik, tetapi yang tampaknya cukup dapat digunakan kembali, mudah beradaptasi dengan banyak situasi, dan mudah diterapkan, adalah sesuatu yang saya sebut "skema switch-a-roo."
Katakanlah Anda memiliki skenario di mana Anda memiliki tabel pencarian besar yang perlu di-refresh secara berkala. Tabel pencarian ini diperlukan di banyak server dan dapat berisi data yang diisi dari sumber eksternal atau pihak ketiga, mis. IP atau data domain, atau dapat mewakili data dari dalam lingkungan Anda sendiri.
Beberapa skenario pertama di mana saya membutuhkan solusi untuk ini adalah membuat metadata dan data yang didenormalisasi tersedia untuk "cache data" hanya-baca – benar-benar hanya contoh SQL Server MSDE (dan kemudian Express) yang diinstal di berbagai server web, sehingga server web ditarik data cache ini secara lokal alih-alih mengganggu sistem OLTP utama. Ini mungkin tampak berlebihan, tetapi aktivitas membaca off-loading jauh dari sistem OLTP utama, dan mampu mengambil koneksi jaringan keluar dari persamaan sepenuhnya, menyebabkan benjolan nyata dalam kinerja semua-sekitar dan, terutama, untuk pengguna akhir .
Server-server ini tidak memerlukan salinan data terbaru; pada kenyataannya, banyak tabel cache hanya diperbarui setiap hari. Tetapi karena sistemnya 24x7, dan beberapa pembaruan ini bisa memakan waktu beberapa menit, mereka sering menghalangi pelanggan nyata melakukan hal-hal nyata pada sistem.
Pendekatan Asli
Pada awalnya, kodenya agak sederhana:kami menghapus baris yang telah dihapus dari sumbernya, memperbarui semua baris yang kami tahu telah berubah, dan menyisipkan semua baris baru. Itu terlihat seperti ini (penanganan kesalahan dll. dihapus untuk singkatnya):
BEGIN TRANSACTION; DELETE dbo.Lookup WHERE [key] NOT IN (SELECT [key] FROM [source]); UPDATE d SET [col] = s.[col] FROM dbo.Lookup AS d INNER JOIN [source] AS s ON d.[key] = s.[key] -- AND [condition to detect change]; INSERT dbo.Lookup([cols]) SELECT [cols] FROM [source] WHERE [key] NOT IN (SELECT [key] FROM dbo.Lookup); COMMIT TRANSACTION;
Tak perlu dikatakan bahwa transaksi ini dapat menyebabkan beberapa masalah kinerja nyata ketika sistem sedang digunakan. Tentunya ada cara lain untuk melakukan ini, tetapi setiap metode yang kami coba sama lambat dan mahalnya. Seberapa lambat dan mahal? "Biarkan saya menghitung pindaian..."
Sejak MERGE yang sudah ada sebelumnya, dan kami telah membuang pendekatan "eksternal" seperti DTS, melalui beberapa pengujian kami memutuskan bahwa akan lebih efisien untuk hanya menghapus tabel dan mengisinya kembali, daripada mencoba dan menyinkronkan ke sumber :
BEGIN TRANSACTION; TRUNCATE TABLE dbo.Lookup; INSERT dbo.Lookup([cols]) SELECT [cols] FROM [source]; COMMIT TRANSACTION;
Sekarang, seperti yang saya jelaskan, kueri dari [sumber] ini dapat memakan waktu beberapa menit, terutama jika semua server web diperbarui secara paralel (kami mencoba untuk mengubah posisi jika memungkinkan). Dan jika pelanggan berada di situs dan mencoba menjalankan kueri yang melibatkan tabel pencarian, mereka harus menunggu transaksi itu selesai. Dalam kebanyakan kasus, jika mereka menjalankan kueri ini pada tengah malam, tidak masalah jika mereka mendapatkan salinan data pencarian kemarin atau hari ini; jadi, membuat mereka menunggu penyegaran tampak konyol, dan sebenarnya menyebabkan sejumlah panggilan dukungan.
Jadi meskipun ini lebih baik, itu jelas jauh dari sempurna.
Solusi Awal Saya :sp_rename
Solusi awal saya, ketika SQL Server 2000 masih keren, adalah membuat tabel "bayangan":
CREATE TABLE dbo.Lookup_Shadow([cols]);
Dengan cara ini saya dapat mengisi tabel bayangan tanpa mengganggu pengguna sama sekali, dan kemudian melakukan penggantian nama tiga arah – operasi cepat, hanya metadata – hanya setelah populasi selesai. Sesuatu seperti ini (sekali lagi, sangat disederhanakan):
TRUNCATE TABLE dbo.Lookup_Shadow; INSERT dbo.Lookup_Shadow([cols]) SELECT [cols] FROM [source]; BEGIN TRANSACTION; EXEC sp_rename N'dbo.Lookup', N'dbo.Lookup_Fake'; EXEC sp_rename N'dbo.Lookup_Shadow', N'dbo.Lookup'; COMMIT TRANSACTION; -- if successful: EXEC sp_rename N'dbo.Lookup_Fake', N'dbo.Lookup_Shadow';
Kelemahan dari pendekatan awal ini adalah sp_rename memiliki pesan keluaran yang tidak dapat ditekan yang memperingatkan Anda tentang bahaya mengganti nama objek. Dalam kasus kami, kami melakukan tugas ini melalui pekerjaan Agen Server SQL, dan kami menangani banyak metadata dan tabel cache lainnya, sehingga riwayat pekerjaan dibanjiri dengan semua pesan yang tidak berguna ini dan benar-benar menyebabkan kesalahan nyata terpotong dari detail riwayat. (Saya mengeluh tentang hal ini pada tahun 2007, tetapi saran saya akhirnya ditolak dan ditutup dengan alasan "Tidak Dapat Diperbaiki.")
Solusi yang Lebih Baik :Skema
Setelah kami memutakhirkan ke SQL Server 2005, saya menemukan perintah fantastis yang disebut CREATE SCHEMA. Itu sepele untuk menerapkan jenis solusi yang sama menggunakan skema alih-alih mengganti nama tabel, dan sekarang riwayat Agen tidak akan tercemar dengan semua pesan yang tidak membantu ini. Pada dasarnya saya membuat dua skema baru:
CREATE SCHEMA fake AUTHORIZATION dbo; CREATE SCHEMA shadow AUTHORIZATION dbo;
Kemudian saya memindahkan tabel Lookup_Shadow ke dalam skema cache, dan menamainya:
ALTER SCHEMA shadow TRANSFER dbo.Lookup_Shadow; EXEC sp_rename N'shadow.Lookup_Shadow', N'Lookup';
(Jika Anda hanya menerapkan solusi ini, Anda akan membuat salinan tabel baru dalam skema, tidak memindahkan tabel yang ada ke sana dan mengganti namanya.)
Dengan dua skema di tempat, dan salinan tabel Pencarian di skema bayangan, penggantian nama tiga arah saya menjadi transfer skema tiga arah:
TRUNCATE TABLE shadow.Lookup; INSERT shadow.Lookup([cols]) SELECT [cols] FROM [source]; -- perhaps an explicit statistics update here BEGIN TRANSACTION; ALTER SCHEMA fake TRANSFER dbo.Lookup; ALTER SCHEMA dbo TRANSFER shadow.Lookup; COMMIT TRANSACTION; ALTER SCHEMA shadow TRANSFER fake.Lookup;
Pada titik ini Anda tentu saja dapat mengosongkan salinan bayangan tabel, namun dalam beberapa kasus saya merasa berguna untuk meninggalkan salinan data "lama" untuk tujuan pemecahan masalah:
TRUNCATE TABLE shadow.Lookup;
Apa pun yang Anda lakukan dengan salinan bayangan, pastikan Anda melakukannya di luar transaksi – dua operasi transfer harus sesingkat dan secepat mungkin.
Beberapa Peringatan
- Tombol Asing
Ini tidak akan berhasil jika tabel pencarian direferensikan oleh kunci asing. Dalam kasus kami, kami tidak menunjukkan batasan apa pun pada tabel cache ini, tetapi jika Anda melakukannya, Anda mungkin harus tetap menggunakan metode intrusif seperti MERGE. Atau gunakan metode append-only dan nonaktifkan atau jatuhkan kunci asing sebelum melakukan modifikasi data apa pun (lalu buat ulang atau aktifkan kembali setelahnya). Jika Anda tetap menggunakan teknik MERGE / UPSERT dan Anda melakukan ini antar server atau, lebih buruk lagi, dari sistem jarak jauh, saya sangat menyarankan untuk mendapatkan data mentah secara lokal daripada mencoba menggunakan metode ini antar server.
- Statistik
Mengganti tabel (menggunakan rename atau transfer skema) akan menyebabkan statistik bolak-balik antara dua salinan tabel, dan ini jelas dapat menjadi masalah untuk rencana. Jadi, Anda dapat mempertimbangkan untuk menambahkan pembaruan statistik eksplisit sebagai bagian dari proses ini.
- Pendekatan Lain
Tentu saja ada cara lain untuk melakukan ini yang belum sempat saya coba. Pergantian partisi dan penggunaan tampilan + sinonim adalah dua pendekatan yang mungkin saya selidiki di masa mendatang untuk pembahasan topik yang lebih menyeluruh. Saya akan tertarik untuk mendengar pengalaman Anda dan bagaimana Anda memecahkan masalah ini di lingkungan Anda. Dan ya, saya menyadari bahwa masalah ini sebagian besar diselesaikan oleh Grup Ketersediaan dan sekunder yang dapat dibaca di SQL Server 2012, tetapi saya menganggapnya sebagai "tembakan trik" jika Anda dapat menyelesaikan masalah tanpa membuang lisensi kelas atas pada masalah, atau mereplikasi seluruh database untuk membuat beberapa tabel berlebihan. :-)
Kesimpulan
Jika Anda dapat hidup dengan keterbatasan di sini, pendekatan ini mungkin lebih baik daripada skenario di mana Anda pada dasarnya membuat tabel offline menggunakan SSIS atau rutinitas MERGE / UPSERT Anda sendiri, tetapi pastikan untuk menguji kedua teknik. Poin terpenting adalah bahwa pengguna akhir yang mengakses tabel harus memiliki pengalaman yang sama persis, kapan saja, bahkan jika mereka mencapai tabel di tengah pembaruan berkala Anda.