Apa perbedaan antara variabel tabel temp dan tabel dalam SQL Server?

Jawaban:

668

Isi

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

DECLARE @T TABLE(X INT)

INSERT INTO @T VALUES(1),(2)

SELECT sys.fn_PhysLocFormatter(%%physloc%%) AS [File:Page:Slot]
FROM @T

Contoh Hasil (menunjukkan lokasi dalam tempdb2 baris disimpan)

File:Page:Slot
----------------
(1:148:0)
(1:148:1)

Lokasi yang logis

@table_variablesberperilaku lebih seolah-olah mereka adalah bagian dari database saat ini daripada #temptabel. Untuk variabel tabel (sejak 2005), jika tidak ditentukan secara eksplisit akan menjadi basis data saat ini sedangkan untuk #temptabel akan menggunakan susunan default tempdb( Lebih detail ). Selain itu tipe data yang ditentukan pengguna dan koleksi XML harus dalam tempdb untuk digunakan untuk #temptabel 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)

Dalam basis data yang terkandung, data tabel sementara dikumpulkan dalam pengumpulan basis data yang terkandung.

  • Semua metadata yang terkait dengan tabel sementara (misalnya, nama tabel dan kolom, indeks, dan sebagainya) akan ada dalam susunan katalog.
  • Kendala yang disebutkan tidak dapat digunakan dalam tabel sementara.
  • Tabel sementara mungkin tidak merujuk ke tipe yang ditentukan pengguna, koleksi skema XML, atau fungsi yang ditentukan pengguna.

Visibilitas ke lingkup yang berbeda

@table_variableshanya dapat diakses dalam kumpulan dan ruang lingkup di mana mereka dinyatakan. #temp_tablesdapat diakses dalam batch anak (pemicu bersarang, prosedur, execpanggilan). #temp_tablesdibuat 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 ( ##temptabel global bisa jadi).

Seumur hidup

@table_variablesdibuat secara implisit ketika kumpulan yang berisi DECLARE @.. TABLEpernyataan 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 DECLAREpernyataan, pembuatan implisit dapat dilihat di bawah ini.

IF (1 = 0)
BEGIN
DECLARE @T TABLE(X INT)
END

--Works fine
SELECT *
FROM @T

#temp_tablesdibuat secara eksplisit ketika CREATE TABLEpernyataan TSQL ditemukan dan dapat dijatuhkan secara eksplisit dengan DROP TABLEatau 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_tablestetapi pembatasan @table_variablestetap mencegah. Overhead pemeliharaan untuk #temptabel 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 #temptabel tetapi seperti yang OBJECT_ID('tempdb..#T')dapat digunakan untuk memasukkan ke dalam tabel sistem dan nama yang dihasilkan secara internal berkorelasi lebih erat dengan nama yang didefinisikan dalam CREATE TABLEpernyataan. Untuk variabel tabel, object_idfungsi 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 menggunakan DBCC PAGEasalkan tidak kosong.

/*Declare a table variable with some unusual options.*/
DECLARE @T TABLE
(
[dba.se] INT IDENTITY PRIMARY KEY NONCLUSTERED,
A INT CHECK (A > 0),
B INT DEFAULT 1,
InRowFiller char(1000) DEFAULT REPLICATE('A',1000),
OffRowFiller varchar(8000) DEFAULT REPLICATE('B',8000),
LOBFiller varchar(max) DEFAULT REPLICATE(cast('C' as varchar(max)),10000),
UNIQUE CLUSTERED (A,B) 
    WITH (FILLFACTOR = 80, 
         IGNORE_DUP_KEY = ON, 
         DATA_COMPRESSION = PAGE, 
         ALLOW_ROW_LOCKS=ON, 
         ALLOW_PAGE_LOCKS=ON)
)

INSERT INTO @T (A)
VALUES (1),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13)

SELECT t.object_id,
       t.name,
       p.rows,
       a.type_desc,
       a.total_pages,
       a.used_pages,
       a.data_pages,
       p.data_compression_desc
FROM   tempdb.sys.partitions AS p
       INNER JOIN tempdb.sys.system_internals_allocation_units AS a
         ON p.hobt_id = a.container_id
       INNER JOIN tempdb.sys.tables AS t
         ON t.object_id = p.object_id
       INNER JOIN tempdb.sys.columns AS c
         ON c.object_id = p.object_id
WHERE  c.name = 'dba.se'

Keluaran

Duplicate key was ignored.

 +-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| object_id |   name    | rows |     type_desc     | total_pages | used_pages | data_pages | data_compression_desc |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | PAGE                  |
| 574625090 | #22401542 |   13 | LOB_DATA          |          24 |         19 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | ROW_OVERFLOW_DATA |          16 |         14 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | NONE                  |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+

Transaksi

Operasi pada @table_variablesdilakukan sebagai transaksi sistem, independen dari setiap transaksi pengguna luar, sedangkan #tempoperasi tabel yang setara akan dilakukan sebagai bagian dari transaksi pengguna itu sendiri. Karena alasan ini suatu ROLLBACKperintah akan memengaruhi sebuah #temptabel tetapi membiarkannya @table_variabletidak tersentuh.

DECLARE @T TABLE(X INT)
CREATE TABLE #T(X INT)

BEGIN TRAN

INSERT #T
OUTPUT INSERTED.X INTO @T
VALUES(1),(2),(3)

/*Both have 3 rows*/
SELECT * FROM #T
SELECT * FROM @T

ROLLBACK

/*Only table variable now has rows*/
SELECT * FROM #T
SELECT * FROM @T
DROP TABLE #T

Penebangan

Keduanya menghasilkan catatan log ke tempdblog 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.

USE tempdb;

/*
Don't run this on a busy server.
Ideally should be no concurrent activity at all
*/
CHECKPOINT;

GO

/*
The 2nd column is binary to allow easier correlation with log output shown later*/
DECLARE @T TABLE ([C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3] INT, B BINARY(10))

INSERT INTO @T
VALUES (1, 0x41414141414141414141), 
       (2, 0x41414141414141414141)

UPDATE @T
SET    B = 0x42424242424242424242

DELETE FROM @T

/*Put allocation_unit_id into CONTEXT_INFO to access in next batch*/
DECLARE @allocId BIGINT, @Context_Info VARBINARY(128)

SELECT @Context_Info = allocation_unit_id,
       @allocId = a.allocation_unit_id 
FROM   sys.system_internals_allocation_units a
       INNER JOIN sys.partitions p
         ON p.hobt_id = a.container_id
       INNER JOIN sys.columns c
         ON c.object_id = p.object_id
WHERE  ( c.name = 'C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3' )

SET CONTEXT_INFO @Context_Info

/*Check log for records related to modifications of table variable itself*/
SELECT Operation,
       Context,
       AllocUnitName,
       [RowLog Contents 0],
       [Log Record Length]
FROM   fn_dblog(NULL, NULL)
WHERE  AllocUnitId = @allocId

GO

/*Check total log usage including updates against system tables*/
DECLARE @allocId BIGINT = CAST(CONTEXT_INFO() AS BINARY(8));

WITH T
     AS (SELECT Operation,
                Context,
                CASE
                  WHEN AllocUnitId = @allocId THEN 'Table Variable'
                  WHEN AllocUnitName LIKE 'sys.%' THEN 'System Base Table'
                  ELSE AllocUnitName
                END AS AllocUnitName,
                [Log Record Length]
         FROM   fn_dblog(NULL, NULL) AS D)
SELECT Operation = CASE
                     WHEN GROUPING(Operation) = 1 THEN 'Total'
                     ELSE Operation
                   END,
       Context,
       AllocUnitName,
       [Size in Bytes] = COALESCE(SUM([Log Record Length]), 0),
       Cnt = COUNT(*)
FROM   T
GROUP  BY GROUPING SETS( ( Operation, Context, AllocUnitName ), ( ) )
ORDER  BY GROUPING(Operation),
          AllocUnitName 

Kembali

Tampilan detail

Tangkapan layar hasil

Ringkasan Ringkasan (termasuk logging untuk drop implisit dan tabel basis sistem)

Tangkapan layar hasil

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 #temptabel tidak dapat dihapus sampai transaksi yang mengandung pengguna selesai sehingga transaksi yang berjalan lama yang pada beberapa titik menulis ke #temptabel akan mencegah pemotongan log di tempdbmana transaksi otonom menelurkan untuk variabel tabel tidak.

Variabel tabel tidak mendukung TRUNCATEsehingga dapat berada pada kerugian logging ketika persyaratannya adalah untuk menghapus semua baris dari tabel (meskipun untuk tabel yang sangat kecil DELETE 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 rowsjumlah yang akurat di sys.partitions. Masalahnya adalah bahwa pada kebanyakan kesempatan pernyataan referensi variabel tabel dikompilasi ketika tabel kosong. Jika pernyataan itu (kembali) dikompilasi setelah @table_variabledihuni maka ini akan digunakan untuk kardinalitas tabel sebagai gantinya (Ini mungkin terjadi karena eksplisit recompileatau mungkin karena pernyataan itu juga referensi objek lain yang menyebabkan kompilasi yang ditangguhkan atau kompilasi ulang.)

DECLARE @T TABLE(I INT);

INSERT INTO @T VALUES(1),(2),(3),(4),(5)

CREATE TABLE #T(I INT)

/*Reference to #T means this statement is subject to deferred compile*/
SELECT * FROM @T WHERE NOT EXISTS(SELECT * FROM #T)

DROP TABLE #T

Paket menunjukkan perkiraan jumlah baris akurat setelah kompilasi ditangguhkan.

Menunjukkan jumlah baris yang akurat

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 #temptabel.

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

RT dihitung sebagai berikut. (n merujuk pada kardinalitas tabel ketika rencana kueri dikompilasi.)

Tabel permanen
- Jika n <= 500, RT = 500.
- Jika n> 500, RT = 500 + 0.20 * n.

Tabel sementara
- Jika n <6, RT = 6.
- Jika 6 <= n <= 500, RT = 500.
- Jika n> 500, RT = 500 + 0.20 * n.
Variabel tabel
- RT tidak ada. Oleh karena itu, kompilasi ulang tidak terjadi karena perubahan kardinalitas variabel tabel. (Tetapi lihat catatan tentang TF 2453 di bawah)

yang KEEP PLANsedikit dapat digunakan untuk mengatur RT untuk #temptabel sama dengan tabel permanen.

Efek bersih dari semua ini adalah bahwa seringkali rencana eksekusi yang dihasilkan untuk #temptabel adalah urutan besarnya lebih baik daripada @table_variablesketika 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 dikoreksi TableCardinalitysaat 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 #temptabel juga dapat dikaitkan dengan kompilasi tambahan hanya karena mereka memungkinkan operasi yang dilarang untuk variabel tabel yang memicu kompilasi (misalnya perubahan DDL CREATE 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.

DECLARE @tv_target TABLE (c11 int, c22 char(100))

DBCC TRACEON(1200,-1,3604)

INSERT INTO @tv_target (c11, c22)

VALUES (1, REPLICATE('A',100)), (2, REPLICATE('A',100))

DBCC TRACEOFF(1200,-1,3604)

Untuk pertanyaan yang SELECTdari variabel tabel Paul White menunjukkan dalam komentar bahwa ini secara otomatis datang dengan NOLOCKpetunjuk implisit . Ini ditunjukkan di bawah ini

DECLARE @T TABLE(X INT); 

SELECT X
FROM @T 
OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8607)

Keluaran

*** Output Tree: (trivial plan) ***

        PhyOp_TableScan TBL: @T Bmk ( Bmk1000) IsRow: COL: IsBaseRow1002  Hints( NOLOCK )

Dampak dari ini pada penguncian mungkin sangat kecil.

SET NOCOUNT ON;

CREATE TABLE #T( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))


DECLARE @T TABLE ( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))

DECLARE @I INT = 0

WHILE (@I < 10000)
BEGIN
INSERT INTO #T DEFAULT VALUES
INSERT INTO @T DEFAULT VALUES
SET @I += 1
END

/*Run once so compilation output doesn't appear in lock output*/
EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEON(1200,3604,-1)
SELECT *, sys.fn_PhysLocFormatter(%%physloc%%)
FROM @T

PRINT '--*--'

EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEOFF(1200,3604,-1)

DROP TABLE #T

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

Process 58 acquiring Sch-S lock on OBJECT: 2:-1325894110:0  (class bit0 ref1) result: OK

--*--
Process 58 acquiring IS lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 acquiring S lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 releasing lock on OBJECT: 2:-1293893996:0 

Output penguncian untuk variabel tabel memang sangat minim karena SQL Server baru saja memperoleh kunci stabilitas skema pada objek. Tetapi untuk sebuah #tempmeja hampir sama ringannya dengan mengeluarkan Skunci level objek . Sebuah NOLOCKpetunjuk atau READ UNCOMMITTEDisolasi tingkat tentu saja dapat ditentukan secara eksplisit ketika bekerja dengan #temptabel juga.

Demikian pula dengan masalah pencatatan transaksi pengguna di sekitarnya dapat berarti bahwa kunci ditahan lebih lama untuk #temptabel. Dengan skrip di bawah ini

    --BEGIN TRAN;   

    CREATE TABLE #T (X INT,Y CHAR(4000) NULL);

    INSERT INTO #T (X) VALUES(1) 

    SELECT CASE resource_type
             WHEN  'OBJECT' THEN OBJECT_NAME(resource_associated_entity_id, 2)
             WHEN  'ALLOCATION_UNIT' THEN (SELECT OBJECT_NAME(object_id, 2)
                                           FROM  tempdb.sys.allocation_units a 
                                           JOIN tempdb.sys.partitions p ON a.container_id = p.hobt_id
                                           WHERE  a.allocation_unit_id = resource_associated_entity_id)
             WHEN 'DATABASE' THEN DB_NAME(resource_database_id)                                      
             ELSE (SELECT OBJECT_NAME(object_id, 2)
                   FROM   tempdb.sys.partitions
                   WHERE  partition_id = resource_associated_entity_id)
           END AS object_name,
           *
    FROM   sys.dm_tran_locks
    WHERE  request_session_id = @@SPID

    DROP TABLE #T

   -- ROLLBACK  

ketika dijalankan di luar transaksi pengguna eksplisit untuk kedua kasus, satu - satunya kunci yang dikembalikan saat memeriksa sys.dm_tran_locksadalah kunci bersama di DATABASE.

Pada uncomment, BEGIN TRAN ... ROLLBACK26 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 NONCLUSTEREDdan 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_options dapat ditentukan dalam deklarasi kendala termasuk DATA_COMPRESSION,, IGNORE_DUP_KEYdan FILLFACTOR(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 INCLUDEd kolom, indeks yang difilter (hingga 2016) atau #temptabel , yang dilakukan tabel (skema partisi harus dibuat di tempdb).

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.

DECLARE @T TABLE (
C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/
C2 INT INDEX IX2 NONCLUSTERED,
       INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/
);

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

DECLARE @T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)

Paralelisme

Kueri yang disisipkan ke (atau memodifikasi) @table_variablestidak dapat memiliki rencana paralel, #temp_tablestidak dibatasi dengan cara ini.

Ada solusi nyata dalam penulisan ulang sebagai berikut yang memungkinkan SELECTbagian terjadi secara paralel tetapi akhirnya menggunakan tabel sementara yang tersembunyi (di belakang layar)

INSERT INTO @DATA ( ... ) 
EXEC('SELECT .. FROM ...')

Tidak ada batasan dalam kueri yang memilih dari variabel tabel seperti yang diilustrasikan dalam jawaban saya di sini

Perbedaan Fungsional Lainnya

  • #temp_tablestidak dapat digunakan di dalam suatu fungsi. @table_variablesdapat digunakan di dalam skalar atau tabel multi-pernyataan UDF.
  • @table_variables tidak dapat menyebutkan batasan.
  • @table_variablestidak dapat SELECT-ed INTO, ALTER-ed, TRUNCATEd atau menjadi target dari DBCCperintah seperti DBCC CHECKIDENTatau dari SET IDENTITY INSERTdan tidak mendukung petunjuk tabel sepertiWITH (FORCESCAN)
  • CHECK kendala pada variabel tabel tidak dipertimbangkan oleh pengoptimal untuk penyederhanaan, predikat tersirat atau deteksi kontradiksi.
  • Variabel tabel tampaknya tidak memenuhi syarat untuk makna optimisasi berbagi rowset yang menghapus dan memperbarui rencana terhadap hal ini dapat menghadapi lebih banyak overhead dan PAGELATCH_EXmenunggu. ( 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.

NB: Perilaku di bawah ini tidak lagi terjadi di SQL Server 2014 atau SQL Server 2012 SP1 / CU10 atau SP2 / CU1 penulis bersemangat tidak lagi ingin menulis halaman ke disk. Lebih detail tentang perubahan itu di SQL Server 2014: tempdb Hidden Performance Gem .

Menjalankan skrip di bawah ini

CREATE TABLE #T(X INT, Filler char(8000) NULL)
INSERT INTO #T(X)
SELECT TOP 250 ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM master..spt_values
DROP TABLE #T

Dan pemantauan menulis ke tempdbfile data dengan Proses Monitor saya tidak melihat (kecuali yang kadang-kadang ke halaman boot database di offset 73.728). Setelah berganti 250ke 251saya mulai melihat tulisan seperti di bawah ini.

ProcMon

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 melihatnyasys.dm_os_buffer_descriptors

DECLARE @T TABLE (
  X        INT,
  [dba.se] CHAR(8000) NULL)

INSERT INTO @T
            (X)
SELECT TOP 251 Row_number() OVER (ORDER BY (SELECT 0))
FROM   master..spt_values

SELECT is_modified,
       Count(*) AS page_count
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = (SELECT a.allocation_unit_id
                                 FROM   tempdb.sys.partitions AS p
                               INNER JOIN tempdb.sys.system_internals_allocation_units AS a
                                          ON p.hobt_id = a.container_id
                                        INNER JOIN tempdb.sys.columns AS c
                                          ON c.object_id = p.object_id
                                 WHERE  c.name = 'dba.se')
GROUP  BY is_modified 

Hasil

is_modified page_count
----------- -----------
0           192
1           61

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 memoryset ke 2000 MBdan DBCC MEMORYSTATUSpelaporan 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.

SELECT Count(*)
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = @allocId
       AND page_type = 'DATA_PAGE' 

Variabel tabel dan #temptabel 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.

Halaman di Buffer Pool

Martin Smith
sumber
Saya telah menemukan bahwa SQL Server memperoleh secara signifikan lebih banyak kait ketika membuat tabel temp (bahkan dengan caching) dibandingkan dengan variabel tabel yang setara. Anda dapat menguji dengan menggunakan debug XE latch_acquired dan membuat tabel dengan sekitar 35 kolom atau lebih. Saya menemukan variabel tabel untuk mengambil 4 kait dan tabel temp untuk mengambil sekitar 70 kait.
Joe Obbish
40

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.

  1. Tabel #temp secara default menggunakan collation default SQL Server instance. Jadi kecuali ditentukan lain, Anda mungkin mengalami masalah membandingkan atau memperbarui nilai antara tabel #temp dan tabel database, jika masterdb memiliki susunan yang berbeda dari database. Lihat: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-turnal-tables-with-the-correct-collation/
  2. Sepenuhnya berdasarkan pengalaman pribadi, memori yang tersedia tampaknya memiliki efek yang berkinerja lebih baik. MSDN merekomendasikan penggunaan variabel tabel untuk menyimpan set hasil yang lebih kecil, tetapi sebagian besar waktu perbedaannya bahkan tidak terlihat. Namun dalam set yang lebih besar, dalam beberapa kasus menjadi jelas bahwa variabel tabel jauh lebih intensif memori dan dapat memperlambat kueri hingga merangkak.
Kahn
sumber
6
Perhatikan juga bahwa susunan pada tabel #temp dapat mewarisi susunan basis data panggilan jika Anda menggunakan SQL Server 2012 dan basis data terkandung.
Aaron Bertrand
Klarifikasi pada # 2 untuk set kecil vs set besar stackoverflow.com/a/14465163/5224021
GibralterTop