Ini tampaknya merupakan area dengan beberapa mitos dan pandangan yang saling bertentangan.
Jadi apa perbedaan antara variabel tabel dan tabel sementara lokal di SQL Server?
sql-server
t-sql
temporary-tables
Martin Smith
sumber
sumber
Jawaban:
Isi
Peringatan
Jawaban ini membahas variabel tabel "klasik" yang diperkenalkan dalam SQL Server 2000. SQL Server 2014 di memori OLTP memperkenalkan Tipe Tabel yang Dioptimalkan Memori. Contoh variabel tabel berbeda dalam banyak hal dengan yang dibahas di bawah ini! ( lebih detail ).
Lokasi penyimpanan
Tidak ada perbedaan. Keduanya disimpan di
tempdb
.Saya telah melihatnya menyarankan bahwa untuk variabel tabel ini tidak selalu terjadi tetapi ini dapat diverifikasi dari bawah
Contoh Hasil (menunjukkan lokasi dalam
tempdb
2 baris disimpan)Lokasi yang logis
@table_variables
berperilaku lebih seolah-olah mereka adalah bagian dari database saat ini daripada#temp
tabel. Untuk variabel tabel (sejak 2005), jika tidak ditentukan secara eksplisit akan menjadi basis data saat ini sedangkan untuk#temp
tabel akan menggunakan susunan defaulttempdb
( Lebih detail ). Selain itu tipe data yang ditentukan pengguna dan koleksi XML harus dalam tempdb untuk digunakan untuk#temp
tabel tetapi variabel tabel dapat menggunakannya dari database saat ini ( Sumber ).SQL Server 2012 memperkenalkan basis data yang terkandung. perilaku tabel sementara dalam perbedaan ini (h / t Aaron)
Visibilitas ke lingkup yang berbeda
@table_variables
hanya dapat diakses dalam kumpulan dan ruang lingkup di mana mereka dinyatakan.#temp_tables
dapat diakses dalam batch anak (pemicu bersarang, prosedur,exec
panggilan).#temp_tables
dibuat di lingkup luar (@@NESTLEVEL=0
) dapat menjangkau bets juga karena mereka bertahan sampai sesi berakhir. Tidak ada tipe objek yang dapat dibuat dalam batch anak dan diakses dalam ruang lingkup panggilan namun seperti yang dibahas selanjutnya (##temp
tabel global bisa jadi).Seumur hidup
@table_variables
dibuat secara implisit ketika kumpulan yang berisiDECLARE @.. TABLE
pernyataan dieksekusi (sebelum kode pengguna apa pun dalam kumpulan tersebut berjalan) dan dihapus secara implisit di akhir.Meskipun parser tidak akan memungkinkan Anda untuk mencoba dan menggunakan variabel tabel sebelum
DECLARE
pernyataan, pembuatan implisit dapat dilihat di bawah ini.#temp_tables
dibuat secara eksplisit ketikaCREATE TABLE
pernyataan TSQL ditemukan dan dapat dijatuhkan secara eksplisit denganDROP TABLE
atau akan dijatuhkan secara implisit ketika batch berakhir (jika dibuat dalam batch anak dengan@@NESTLEVEL > 0
) atau ketika sesi berakhir sebaliknya.NB: Dalam rutinitas tersimpan kedua jenis objek dapat di-cache daripada berulang kali membuat dan menjatuhkan tabel baru. Ada pembatasan kapan caching ini dapat terjadi namun yang mungkin dilanggar
#temp_tables
tetapi pembatasan@table_variables
tetap mencegah. Overhead pemeliharaan untuk#temp
tabel cache sedikit lebih besar daripada untuk variabel tabel seperti yang diilustrasikan di sini .Metadata objek
Ini pada dasarnya sama untuk kedua jenis objek. Ini disimpan dalam tabel basis sistem di
tempdb
. Akan lebih mudah untuk melihat#temp
tabel tetapi seperti yangOBJECT_ID('tempdb..#T')
dapat digunakan untuk memasukkan ke dalam tabel sistem dan nama yang dihasilkan secara internal berkorelasi lebih erat dengan nama yang didefinisikan dalamCREATE TABLE
pernyataan. Untuk variabel tabel,object_id
fungsi tidak bekerja dan nama internal sepenuhnya dihasilkan oleh sistem tanpa hubungan dengan nama variabel. Di bawah ini menunjukkan metadata masih ada namun dengan memasukkan nama kolom (semoga unik). Untuk tabel tanpa nama kolom unik, object_id dapat ditentukan menggunakanDBCC PAGE
asalkan tidak kosong.Keluaran
Transaksi
Operasi pada
@table_variables
dilakukan sebagai transaksi sistem, independen dari setiap transaksi pengguna luar, sedangkan#temp
operasi tabel yang setara akan dilakukan sebagai bagian dari transaksi pengguna itu sendiri. Karena alasan ini suatuROLLBACK
perintah akan memengaruhi sebuah#temp
tabel tetapi membiarkannya@table_variable
tidak tersentuh.Penebangan
Keduanya menghasilkan catatan log ke
tempdb
log transaksi. Kesalahpahaman yang umum adalah bahwa ini bukan kasus untuk variabel tabel sehingga skrip yang menunjukkan ini di bawah ini, itu menyatakan variabel tabel, menambahkan beberapa baris kemudian memperbarui dan menghapusnya.Karena variabel tabel dibuat dan dihapus secara implisit pada awal dan akhir batch, perlu menggunakan beberapa batch untuk melihat log lengkap.
Kembali
Tampilan detail
Ringkasan Ringkasan (termasuk logging untuk drop implisit dan tabel basis sistem)
Sejauh yang saya bisa membedakan operasi pada keduanya menghasilkan jumlah penebangan yang kira-kira sama.
Sementara jumlah logging sangat mirip satu perbedaan penting adalah bahwa catatan log terkait dengan
#temp
tabel tidak dapat dihapus sampai transaksi yang mengandung pengguna selesai sehingga transaksi yang berjalan lama yang pada beberapa titik menulis ke#temp
tabel akan mencegah pemotongan log ditempdb
mana transaksi otonom menelurkan untuk variabel tabel tidak.Variabel tabel tidak mendukung
TRUNCATE
sehingga dapat berada pada kerugian logging ketika persyaratannya adalah untuk menghapus semua baris dari tabel (meskipun untuk tabel yang sangat kecilDELETE
dapat bekerja dengan lebih baik )Kardinalitas
Banyak dari rencana eksekusi yang melibatkan variabel tabel akan memperlihatkan satu baris yang diestimasi sebagai output dari mereka. Memeriksa properti variabel tabel menunjukkan bahwa SQL Server percaya variabel tabel memiliki baris nol (Mengapa memperkirakan 1 baris akan dipancarkan dari tabel baris nol dijelaskan oleh @Paul White di sini ).
Namun hasil yang ditunjukkan pada bagian sebelumnya memang menunjukkan
rows
jumlah yang akurat disys.partitions
. Masalahnya adalah bahwa pada kebanyakan kesempatan pernyataan referensi variabel tabel dikompilasi ketika tabel kosong. Jika pernyataan itu (kembali) dikompilasi setelah@table_variable
dihuni maka ini akan digunakan untuk kardinalitas tabel sebagai gantinya (Ini mungkin terjadi karena eksplisitrecompile
atau mungkin karena pernyataan itu juga referensi objek lain yang menyebabkan kompilasi yang ditangguhkan atau kompilasi ulang.)Paket menunjukkan perkiraan jumlah baris akurat setelah kompilasi ditangguhkan.
Dalam SQL Server 2012 SP2, bendera jejak 2453 diperkenalkan. Rincian lebih lanjut berada di bawah "Mesin Relasional" di sini .
Ketika flag trace ini diaktifkan, ia dapat menyebabkan kompilasi ulang otomatis untuk memperhitungkan perubahan kardinalitas sebagaimana dibahas lebih lanjut dengan sangat singkat.
NB: Pada Azure di tingkat kompatibilitas, kompilasi pernyataan tersebut sekarang ditangguhkan hingga eksekusi pertama . Ini berarti bahwa itu tidak akan lagi tunduk pada masalah estimasi baris nol.
Tidak ada statistik kolom
Akan tetapi, memiliki kardinalitas tabel yang lebih akurat tidak berarti bahwa perkiraan jumlah baris akan lebih akurat (kecuali melakukan operasi pada semua baris dalam tabel). SQL Server sama sekali tidak memelihara statistik kolom untuk variabel tabel sehingga akan kembali pada dugaan berdasarkan predikat perbandingan (mis. 10% dari tabel akan dikembalikan untuk
=
kolom yang tidak unik atau 30% untuk>
perbandingan). Sebaliknya statistik kolom yang dipertahankan untuk#temp
tabel.SQL Server memelihara hitungan jumlah modifikasi yang dibuat untuk setiap kolom. Jika jumlah modifikasi sejak rencana disusun melebihi ambang recompilation (RT) maka rencana akan dikompilasi ulang dan statistik diperbarui. RT tergantung pada jenis dan ukuran tabel.
Dari Plan Caching di SQL Server 2008
yang
KEEP PLAN
sedikit dapat digunakan untuk mengatur RT untuk#temp
tabel sama dengan tabel permanen.Efek bersih dari semua ini adalah bahwa seringkali rencana eksekusi yang dihasilkan untuk
#temp
tabel adalah urutan besarnya lebih baik daripada@table_variables
ketika banyak baris terlibat karena SQL Server memiliki informasi yang lebih baik untuk bekerja dengannya.NB1: Variabel tabel tidak memiliki statistik tetapi masih dapat menimbulkan peristiwa kompilasi "Statistik Berubah" di bawah bendera jejak 2453 (tidak berlaku untuk rencana "sepele") Ini tampaknya terjadi di bawah ambang batas kompilasi ulang yang sama seperti yang ditunjukkan untuk tabel temp di atas dengan satu lagi yang jika
N=0 -> RT = 1
. yaitu semua pernyataan yang dikompilasi ketika variabel tabel kosong akan berakhir mendapatkan kompilasi ulang dan dikoreksiTableCardinality
saat pertama kali dieksekusi ketika non kosong. Kardinalitas tabel waktu kompilasi disimpan dalam rencana dan jika pernyataan dieksekusi lagi dengan kardinalitas yang sama (baik karena aliran pernyataan kontrol atau penggunaan kembali rencana cache) tidak ada kompilasi ulang yang terjadi.NB2: Untuk tabel sementara yang di-cache dalam prosedur tersimpan cerita rekompilasi jauh lebih rumit daripada yang dijelaskan di atas. Lihat Tabel Sementara dalam Prosedur yang Disimpan untuk semua detail berdarah.
Rekompilasi
Serta kompilasi berdasarkan modifikasi yang dijelaskan di atas
#temp
tabel juga dapat dikaitkan dengan kompilasi tambahan hanya karena mereka memungkinkan operasi yang dilarang untuk variabel tabel yang memicu kompilasi (misalnya perubahan DDLCREATE INDEX
,ALTER TABLE
)Mengunci
Telah dinyatakan bahwa variabel tabel tidak berpartisipasi dalam penguncian. Ini bukan kasusnya. Menjalankan output di bawah ini ke tab pesan SSMS rincian kunci yang diambil dan dirilis untuk pernyataan penyisipan.
Untuk pertanyaan yang
SELECT
dari variabel tabel Paul White menunjukkan dalam komentar bahwa ini secara otomatis datang denganNOLOCK
petunjuk implisit . Ini ditunjukkan di bawah iniKeluaran
Dampak dari ini pada penguncian mungkin sangat kecil.
Tidak satu pun dari hasil ini dalam urutan kunci indeks yang menunjukkan bahwa SQL Server menggunakan pemindaian alokasi yang dialokasikan untuk keduanya.
Saya menjalankan skrip di atas dua kali dan hasil untuk menjalankan kedua di bawah
Output penguncian untuk variabel tabel memang sangat minim karena SQL Server baru saja memperoleh kunci stabilitas skema pada objek. Tetapi untuk sebuah
#temp
meja hampir sama ringannya dengan mengeluarkanS
kunci level objek . SebuahNOLOCK
petunjuk atauREAD UNCOMMITTED
isolasi tingkat tentu saja dapat ditentukan secara eksplisit ketika bekerja dengan#temp
tabel juga.Demikian pula dengan masalah pencatatan transaksi pengguna di sekitarnya dapat berarti bahwa kunci ditahan lebih lama untuk
#temp
tabel. Dengan skrip di bawah iniketika dijalankan di luar transaksi pengguna eksplisit untuk kedua kasus, satu - satunya kunci yang dikembalikan saat memeriksa
sys.dm_tran_locks
adalah kunci bersama diDATABASE
.Pada uncomment,
BEGIN TRAN ... ROLLBACK
26 baris dikembalikan yang menunjukkan bahwa kunci dipegang pada objek itu sendiri dan pada baris sistem tabel untuk memungkinkan rollback dan mencegah transaksi lain dari membaca data yang tidak dikomit. Operasi variabel tabel ekuivalen tidak tunduk pada kembalikan dengan transaksi pengguna dan tidak perlu menahan kunci-kunci ini untuk kami periksa dalam pernyataan berikutnya tetapi melacak kunci yang diperoleh dan dirilis di Profiler atau menggunakan jejak bendera 1200 menunjukkan banyak peristiwa penguncian masih terjadi.Indeks
Untuk versi sebelum indeks SQL Server 2014 hanya dapat dibuat secara implisit pada variabel tabel sebagai efek samping dari menambahkan batasan unik atau kunci utama. Ini tentu saja berarti bahwa hanya indeks unik yang didukung. Indeks non-clustered non unik pada tabel dengan indeks clustered unik dapat disimulasikan namun dengan hanya mendeklarasikannya
UNIQUE NONCLUSTERED
dan menambahkan kunci CI ke akhir kunci NCI yang diinginkan (SQL Server tetap akan melakukan ini di belakang layar meskipun tidak unik NCI dapat ditentukan)Seperti yang diperlihatkan sebelumnya berbagai
index_option
s dapat ditentukan dalam deklarasi kendala termasukDATA_COMPRESSION
,,IGNORE_DUP_KEY
danFILLFACTOR
(meskipun tidak ada gunanya menetapkan bahwa satu karena hanya akan membuat perbedaan pada indeks membangun kembali dan Anda tidak dapat membangun kembali indeks pada variabel tabel!)Selain itu variabel tabel tidak mendukung
INCLUDE
d kolom, indeks yang difilter (hingga 2016) atau#temp
tabel , yang dilakukan tabel (skema partisi harus dibuat ditempdb
).Indeks dalam SQL Server 2014
Indeks non unik dapat dinyatakan sebaris dalam definisi variabel tabel di SQL Server 2014. Contoh sintaks untuk ini adalah di bawah ini.
Indeks dalam SQL Server 2016
Dari CTP 3.1 sekarang dimungkinkan untuk mendeklarasikan indeks yang difilter untuk variabel tabel. Dengan RTM itu mungkin kasus yang menyertakan kolom juga diperbolehkan meskipun mereka kemungkinan tidak akan masuk ke SQL16 karena kendala sumber daya
Paralelisme
Kueri yang disisipkan ke (atau memodifikasi)
@table_variables
tidak dapat memiliki rencana paralel,#temp_tables
tidak dibatasi dengan cara ini.Ada solusi nyata dalam penulisan ulang sebagai berikut yang memungkinkan
SELECT
bagian terjadi secara paralel tetapi akhirnya menggunakan tabel sementara yang tersembunyi (di belakang layar)Tidak ada batasan dalam kueri yang memilih dari variabel tabel seperti yang diilustrasikan dalam jawaban saya di sini
Perbedaan Fungsional Lainnya
#temp_tables
tidak dapat digunakan di dalam suatu fungsi.@table_variables
dapat digunakan di dalam skalar atau tabel multi-pernyataan UDF.@table_variables
tidak dapat menyebutkan batasan.@table_variables
tidak dapatSELECT
-edINTO
,ALTER
-ed,TRUNCATE
d atau menjadi target dariDBCC
perintah sepertiDBCC CHECKIDENT
atau dariSET IDENTITY INSERT
dan tidak mendukung petunjuk tabel sepertiWITH (FORCESCAN)
CHECK
kendala pada variabel tabel tidak dipertimbangkan oleh pengoptimal untuk penyederhanaan, predikat tersirat atau deteksi kontradiksi.PAGELATCH_EX
menunggu. ( Contoh )Hanya Memori?
Seperti yang dinyatakan di awal, keduanya disimpan di halaman
tempdb
. Namun saya tidak membahas apakah ada perbedaan perilaku ketika menulis halaman ini ke disk.Saya telah melakukan sejumlah kecil pengujian pada ini sekarang dan sejauh ini tidak melihat perbedaan seperti itu. Dalam tes khusus yang saya lakukan pada contoh saya SQL Server 250 halaman tampaknya menjadi titik potong sebelum file data ditulis.
Menjalankan skrip di bawah ini
Dan pemantauan menulis ke
tempdb
file data dengan Proses Monitor saya tidak melihat (kecuali yang kadang-kadang ke halaman boot database di offset 73.728). Setelah berganti250
ke251
saya mulai melihat tulisan seperti di bawah ini.Tangkapan layar di atas menunjukkan 5 * 32 halaman menulis dan satu halaman menulis yang menunjukkan bahwa 161 halaman ditulis ke disk. Saya mendapat titik potong yang sama dari 250 halaman ketika menguji dengan variabel tabel juga. Script di bawah ini menunjukkan cara yang berbeda dengan melihatnya
sys.dm_os_buffer_descriptors
Hasil
Menunjukkan bahwa 192 halaman ditulis ke disk dan bendera kotor dibersihkan. Ini juga menunjukkan bahwa sedang ditulis ke disk tidak berarti bahwa halaman akan segera diusir dari kumpulan buffer. Kueri terhadap variabel tabel ini masih bisa dipenuhi sepenuhnya dari memori.
Pada server idle dengan
max server memory
set ke2000 MB
danDBCC MEMORYSTATUS
pelaporan Buffer Pool Pages Dialokasikan sebagai sekitar 1.843.000 KB (c. 23.000 halaman) saya menyisipkan ke tabel di atas dalam batch 1.000 baris / halaman dan untuk setiap iterasi yang direkam.Variabel tabel dan
#temp
tabel memberikan grafik yang hampir sama dan berhasil memaksimalkan buffer pool sebelum mencapai titik bahwa mereka tidak sepenuhnya disimpan dalam memori sehingga tampaknya tidak ada batasan khusus pada seberapa banyak memori baik dapat mengkonsumsi.sumber
Ada beberapa hal yang ingin saya tunjukkan lebih berdasarkan pengalaman tertentu daripada belajar. Sebagai seorang DBA, saya sangat baru jadi tolong perbaiki saya jika diperlukan.
sumber