Pada artikel ini, kita akan menyentuh topik kinerja variabel tabel. Di SQL Server, kita dapat membuat variabel yang akan beroperasi sebagai tabel lengkap. Mungkin, database lain memiliki kemampuan yang sama, namun, saya menggunakan variabel seperti itu hanya di MS SQL Server.
Dengan demikian, Anda dapat menulis sebagai berikut:
declare @t as table (int value)
Di sini, kami mendeklarasikan variabel @t sebagai tabel yang akan berisi kolom Nilai tunggal dari tipe Integer. Dimungkinkan untuk membuat tabel yang lebih kompleks, namun, dalam contoh kami, satu kolom sudah cukup untuk menjelajahi pengoptimalan.
Sekarang, kita dapat menggunakan variabel ini dalam kueri kita. Kita dapat menambahkan banyak data ke dalamnya dan melakukan pengambilan data dari variabel ini:
insert into @t select UserID from User or select * from @t
Saya perhatikan bahwa variabel tabel digunakan saat diperlukan untuk mengambil data untuk banyak pilihan. Misalnya, ada kueri dalam kode yang mengembalikan pengguna situs. Sekarang, Anda mengumpulkan ID semua pengguna, menambahkannya ke variabel tabel dan dapat mencari alamat untuk pengguna ini. Mungkin, seseorang mungkin bertanya mengapa kami tidak menjalankan satu kueri di database dan langsung mendapatkan semuanya? Saya punya contoh sederhana.
Asumsikan bahwa pengguna berasal dari layanan Web, sementara alamat mereka disimpan di database Anda. Dalam hal ini, tidak ada jalan keluar. Kami mendapatkan banyak ID pengguna dari layanan, dan untuk menghindari kueri database, seseorang memutuskan bahwa lebih mudah untuk menambahkan semua ID ke parameter kueri sebagai variabel tabel dan kueri akan terlihat rapi:
select * from @t as users join Address a on a.UserID = users.UserID os
Semua ini bekerja dengan benar. Dalam kode C#, Anda dapat dengan cepat menggabungkan hasil kedua array data menjadi satu objek menggunakan LINQ. Namun, kinerja kueri mungkin terganggu.
Faktanya adalah bahwa variabel tabel tidak dirancang untuk memproses data dalam jumlah besar. Jika saya tidak salah, pengoptimal kueri akan selalu menggunakan metode eksekusi LOOP. Jadi, untuk setiap ID dari @t, pencarian di tabel Alamat akan terjadi. Jika ada 1000 catatan di @t, server akan memindai Alamat 1000 kali.
Dalam hal eksekusi, karena jumlah pemindaian yang gila-gilaan, server hanya berhenti mencoba mencari data.
Jauh lebih efektif untuk memindai seluruh tabel Alamat dan menemukan semua pengguna sekaligus. Metode ini disebut MERGE. Namun, SQL Server memilihnya ketika ada banyak data yang diurutkan. Dalam hal ini, pengoptimal tidak mengetahui berapa banyak dan data apa yang akan ditambahkan ke variabel, dan apakah ada pengurutan karena variabel tersebut tidak menyertakan indeks.
Jika ada sedikit data dalam variabel tabel dan Anda tidak memasukkan ribuan baris di dalamnya, semuanya baik-baik saja. Namun, jika Anda suka menggunakan variabel tersebut dan menambahkan sejumlah besar data ke dalamnya, Anda harus melanjutkan membaca.
Bahkan jika Anda mengganti variabel tabel dengan SQL, itu akan sangat mempercepat kinerja kueri:
select * from ( Select 10377 as UserID Union all Select 73736 Union all Select 7474748 …. ) as users join Address a on a.UserID = users.UserID
Mungkin ada ribuan pernyataan SELECT seperti itu dan teks kueri akan sangat besar, tetapi akan dieksekusi ribuan kali lebih cepat untuk data yang banyak karena SQL Server dapat memilih rencana eksekusi yang efektif.
Kueri ini tidak terlihat bagus. Namun, rencana eksekusinya tidak dapat di-cache karena hanya mengubah satu ID akan mengubah seluruh teks kueri juga dan parameter tidak dapat digunakan.
Saya pikir Microsoft tidak mengharapkan pengguna untuk menggunakan variabel tabular dengan cara ini, tetapi ada solusi yang bagus.
Ada beberapa cara untuk mengatasi masalah ini. Namun, menurut saya, yang paling efektif dalam hal kinerja adalah menambahkan OPTION (RECOMPILE) di akhir query:
select * from @t as users join Address a on a.UserID = users.UserID OPTION (RECOMPILE)
Opsi ini ditambahkan sekali di akhir kueri bahkan setelah ORDER BY. Tujuan dari opsi ini adalah untuk membuat SQL Server mengkompilasi ulang kueri pada setiap eksekusi.
Jika kita mengukur kinerja kueri setelah itu, kemungkinan besar waktu akan berkurang untuk melakukan pencarian. Dengan data yang besar, peningkatan performa bisa signifikan, dari puluhan menit hingga detik. Sekarang, server mengkompilasi kodenya sebelum menjalankan setiap kueri dan tidak menggunakan rencana eksekusi dari cache, tetapi menghasilkan yang baru, tergantung pada jumlah data dalam variabel, dan ini biasanya sangat membantu.
Kekurangannya adalah bahwa rencana eksekusi tidak disimpan dan server harus mengkompilasi kueri dan mencari rencana eksekusi yang efektif setiap kali. Namun, saya belum melihat kueri yang prosesnya memakan waktu lebih dari 100 md.
Apakah ide yang buruk untuk menggunakan variabel tabel? Tidak, bukan itu. Ingatlah bahwa mereka tidak dibuat untuk data besar. Terkadang, lebih baik membuat tabel sementara, jika ada banyak data, dan memasukkan data ke dalam tabel ini, atau bahkan membuat indeks dengan cepat. Saya harus melakukan ini dengan laporan, meskipun hanya sekali. Saat itu, saya mengurangi waktu untuk membuat satu laporan dari 3 jam menjadi 20 menit.
Saya lebih suka menggunakan satu kueri besar daripada membaginya menjadi beberapa kueri dan menyimpan hasil dalam variabel. Izinkan SQL Server untuk menyetel kinerja kueri besar dan itu tidak akan mengecewakan Anda. Harap dicatat bahwa Anda harus menggunakan variabel tabel hanya dalam kasus ekstrim ketika Anda benar-benar melihat manfaatnya.