ID pertama: Ini adalah bidang yang paling selektif (yaitu paling unik). Tetapi dengan menjadi bidang kenaikan otomatis (atau acak jika masih menggunakan GUID), data masing-masing pelanggan tersebar di setiap tabel. Ini berarti bahwa ada kalanya pelanggan membutuhkan 100 baris, dan itu membutuhkan hampir 100 halaman data yang dibaca dari disk (tidak cepat) ke dalam Buffer Pool (mengambil lebih banyak ruang daripada 10 halaman data). Hal ini juga meningkatkan pertikaian pada halaman data karena akan lebih sering bahwa banyak pelanggan perlu memperbarui halaman data yang sama.
Namun, Anda biasanya tidak mengalami sebanyak masalah parameter sniffing / rencana cache yang jelek karena statistik lintas nilai ID yang berbeda cukup konsisten. Anda mungkin tidak mendapatkan rencana yang paling optimal, tetapi Anda tidak akan mendapatkan rencana yang mengerikan. Metode ini pada dasarnya mengorbankan kinerja (sedikit) di semua pelanggan untuk mendapatkan manfaat dari masalah yang lebih jarang.
TenantID pertama:Ini sangat tidak selektif sama sekali. Mungkin ada sedikit variasi di 1 juta baris jika Anda hanya memiliki 100 TenantID. Tetapi statistik untuk kueri ini lebih akurat karena SQL Server akan tahu bahwa permintaan untuk penyewa A akan menarik kembali 500.000 baris tetapi permintaan yang sama untuk penyewa B hanya 50 baris. Di sinilah titik nyeri utamanya. Metode ini sangat meningkatkan kemungkinan memiliki masalah mengendus parameter di mana proses pertama dari Prosedur Tersimpan adalah untuk Penyewa A dan bertindak dengan tepat berdasarkan Pengoptimal Kueri melihat statistik tersebut dan mengetahui itu perlu efisien mendapatkan 500k baris. Tetapi ketika Tenant B, dengan hanya 50 baris, berjalan, rencana eksekusi tidak lagi sesuai, dan pada kenyataannya, sangat tidak pantas. DAN, karena data tidak dimasukkan dalam urutan bidang terkemuka,
Namun, untuk TenantID pertama yang menjalankan Prosedur Tersimpan, kinerja harus lebih baik daripada dalam pendekatan lain karena data (setidaknya setelah melakukan pemeliharaan indeks) akan diatur secara fisik dan logis sehingga jauh lebih sedikit halaman data diperlukan untuk memenuhi pertanyaan. Ini berarti I / O fisik lebih sedikit, lebih sedikit bacaan logis, lebih sedikit pertentangan antara Penyewa untuk halaman data yang sama, lebih sedikit ruang terbuang yang digunakan dalam Buffer Pool (karenanya meningkatkan Page Life Expectancy) dll.
Ada dua biaya utama untuk mendapatkan peningkatan kinerja ini. Yang pertama tidak begitu sulit: Anda harus melakukan pemeliharaan indeks secara teratur untuk mengatasi peningkatan fragmentasi. Yang kedua agak kurang menyenangkan.
Untuk mengatasi peningkatan masalah sniffing parameter, Anda harus memisahkan rencana eksekusi antara Penyewa. Pendekatan sederhana adalah untuk digunakan WITH RECOMPILE
pada procs atau OPTION (RECOMPILE)
petunjuk kueri, tetapi itu adalah hit pada kinerja yang bisa menghapus semua keuntungan yang dibuat dengan menempatkan TenantID
pertama. Metode yang saya temukan bekerja paling baik adalah dengan menggunakan Dynamic SQL parameterized via sp_executesql
. Alasan untuk membutuhkan SQL Dinamis adalah untuk memungkinkan menggabungkan TenantID ke dalam teks kueri, sementara semua predikat lain yang biasanya menjadi parameter masih merupakan parameter. Misalnya, jika Anda mencari pesanan tertentu, Anda akan melakukan sesuatu seperti:
DECLARE @GetOrderSQL NVARCHAR(MAX);
SET @GetOrderSQL = N'
SELECT ord.field1, ord.field2, etc.
FROM dbo.Orders ord
WHERE ord.TenantID = ' + CONVERT(NVARCHAR(10), @TenantID) + N'
AND ord.OrderID = @OrderID_dyn;
';
EXEC sp_executesql
@GetOrderSQL,
N'@OrderID_dyn INT',
@OrderID_dyn = @OrderID;
Efek yang dimilikinya adalah membuat rencana permintaan yang dapat digunakan kembali hanya untuk TenantID yang akan cocok dengan volume data Tenant tertentu. Jika penyewa yang sama mengeksekusi prosedur yang tersimpan lagi untuk yang lain @OrderID
maka itu akan menggunakan kembali rencana permintaan yang di-cache. Penyewa berbeda yang menjalankan Prosedur Disimpan yang sama akan menghasilkan teks kueri yang hanya berbeda dalam nilai TenantID, tetapi setiap perbedaan dalam teks kueri cukup untuk menghasilkan rencana yang berbeda. Dan rencana yang dihasilkan untuk Tenant B tidak hanya akan cocok dengan volume data untuk Tenant B, tetapi juga akan dapat digunakan kembali untuk Tenant B untuk nilai yang berbeda dari @OrderID
(karena predikat itu masih parameter).
Kelemahan dari pendekatan ini adalah:
- Ini sedikit lebih banyak pekerjaan daripada hanya mengetikkan query sederhana (tetapi tidak semua query harus Dynamic SQL, hanya yang akhirnya memiliki masalah sniffing parameter).
- Bergantung pada berapa banyak Penyewa pada suatu sistem, ia meningkatkan ukuran cache rencana karena setiap permintaan sekarang membutuhkan 1 paket per TenantID yang memanggilnya. Ini mungkin bukan masalah, tetapi setidaknya sesuatu yang harus diperhatikan.
SQL dinamis memecah rantai kepemilikan, yang berarti akses baca / tulis ke tabel tidak dapat diasumsikan dengan memiliki EXECUTE
izin pada Prosedur Tersimpan. Perbaikan yang mudah tapi kurang aman hanya untuk memberikan akses langsung ke tabel. Ini tentu saja tidak ideal, tetapi itu biasanya merupakan pertukaran dengan cepat dan mudah. Pendekatan yang lebih aman adalah dengan menggunakan keamanan berbasis Sertifikat. Artinya, membuat sertifikat, kemudian membuat Pengguna dari yang Sertifikat, memberikan bahwa pengguna hak akses yang diinginkan (User berbasis sertifikat atau Login tidak dapat terhubung ke SQL Server sendiri), dan kemudian menandatangani Stored Procedures yang menggunakan Dynamic SQL dengan itu Sertifikat yang sama melalui ADD SIGNATURE .
Untuk informasi lebih lanjut tentang penandatanganan modul dan Sertifikat, silakan lihat: ModuleSigning.Info
(ID, TenantID)
dan Anda juga membuat indeks non-berkerumun aktif(TenantID, ID)
, atau hanya(TenantID)
untuk memiliki statistik yang akurat untuk kueri yang memproses sebagian besar baris penyewa tunggal?