ความแตกต่างระหว่างโต๊ะ temp และตัวแปร table ใน SQL Server คืออะไร?


452

ดูเหมือนว่าจะเป็นพื้นที่ที่มีตำนานและมุมมองที่ขัดแย้งกันอยู่บ้าง

ดังนั้นความแตกต่างระหว่างตัวแปรตารางและตารางชั่วคราวใน SQL Server คืออะไร?


คำตอบ:


668

สารบัญ

สารบัญ

ข้อแม้

คำตอบนี้กล่าวถึงตัวแปรตาราง "คลาสสิค" ที่แนะนำใน SQL Server 2000 SQL Server 2014 ในหน่วยความจำ OLTP แนะนำประเภทตารางที่ปรับให้เหมาะสมหน่วยความจำ อินสแตนซ์ตัวแปรของตารางเหล่านั้นแตกต่างกันไปตามที่กล่าวไว้ด้านล่าง! ( รายละเอียดเพิ่มเติม )

ที่เก็บสินค้า

ไม่แตกต่าง. tempdbทั้งสองจะถูกเก็บไว้ใน

ฉันเคยเห็นมันบอกว่าสำหรับตัวแปรตารางนี้ไม่ได้เป็นกรณีเสมอไป แต่สามารถตรวจสอบได้จากด้านล่าง

DECLARE @T TABLE(X INT)

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

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

ตัวอย่างผลลัพธ์ ( tempdbจัดเก็บตำแหน่งที่แสดงใน2 แถว)

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

ตำแหน่งโลจิคัล

@table_variablesทำตัวเหมือนเป็นส่วนหนึ่งของฐานข้อมูลปัจจุบันมากกว่า#tempตาราง สำหรับตัวแปรตาราง (ตั้งแต่ปี 2005) การจัดเรียงคอลัมน์หากไม่ได้ระบุไว้อย่างชัดเจนจะเป็นฐานข้อมูลปัจจุบันส่วน#tempตารางจะใช้การจัดเรียงเริ่มต้นของtempdb( รายละเอียดเพิ่มเติม ) นอกจากนี้ชนิดข้อมูลที่ผู้ใช้กำหนดเองและคอลเลกชัน XML จะต้องอยู่ใน tempdb เพื่อใช้สำหรับ#tempตาราง แต่ตัวแปรตารางสามารถใช้พวกเขาจากฐานข้อมูลปัจจุบัน ( ที่มา )

SQL Server 2012 แนะนำฐานข้อมูลที่มีอยู่ พฤติกรรมของตารางชั่วคราวในสิ่งเหล่านี้แตกต่าง (h / t แอรอน)

ในฐานข้อมูลที่มีอยู่ชั่วคราวตารางข้อมูลจะถูกเรียงในการเปรียบเทียบของฐานข้อมูลที่มีอยู่

  • ข้อมูลเมตาทั้งหมดที่เกี่ยวข้องกับตารางชั่วคราว (ตัวอย่างเช่นชื่อตารางและคอลัมน์ดัชนีและอื่น ๆ ) จะอยู่ในการจัดเรียงแคตาล็อก
  • ข้อ จำกัด ที่มีชื่อไม่สามารถใช้ในตารางชั่วคราว
  • ตารางชั่วคราวอาจไม่อ้างถึงประเภทที่ผู้ใช้กำหนดคอลเลกชันสกีมา XML หรือฟังก์ชั่นที่ผู้ใช้กำหนด

การเปิดเผยขอบเขตที่แตกต่าง

@table_variablesสามารถเข้าถึงได้ภายในชุดและขอบเขตที่มีการประกาศเท่านั้น #temp_tablesสามารถเข้าถึงได้ภายในชุดลูก (ทริกเกอร์ซ้อนขั้นตอนการexecโทร) #temp_tablesสร้างที่ขอบเขตด้านนอก ( @@NESTLEVEL=0) สามารถขยายแบตช์ได้เช่นกันจนกว่าจะสิ้นสุดเซสชัน ไม่สามารถสร้างวัตถุประเภทใดก็ได้ในชุดลูกและเข้าถึงได้ในขอบเขตการโทรอย่างไรก็ตามตามที่กล่าวไว้ถัดไป ( ##tempตารางทั่วโลกสามารถเป็นได้)

ตลอดชีวิต

@table_variablesจะถูกสร้างขึ้นโดยปริยายเมื่อแบทช์ที่มีDECLARE @.. TABLEคำสั่งถูกเรียกใช้งาน (ก่อนที่รหัสผู้ใช้ในแบตช์นั้นจะทำงาน) และจะถูกทิ้งโดยปริยายในตอนท้าย

แม้ว่า parser จะไม่อนุญาตให้คุณลองและใช้ตัวแปร table ก่อนที่DECLAREคำแถลงการสร้างโดยนัยสามารถเห็นได้ด้านล่าง

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

--Works fine
SELECT *
FROM @T

#temp_tablesจะถูกสร้างขึ้นอย่างชัดเจนเมื่อCREATE TABLEพบคำสั่งTSQL และสามารถถูกดร็อปอย่างชัดเจนด้วยDROP TABLEหรือจะถูกทิ้งโดยปริยายเมื่อแบทช์สิ้นสุดลง (หากสร้างในแบทช์ย่อยด้วย@@NESTLEVEL > 0) หรือเมื่อเซสชันสิ้นสุดลงมิฉะนั้น

หมายเหตุ: ภายในรูทีนที่เก็บไว้ทั้งสองประเภทของวัตถุสามารถแคชได้แทนที่จะสร้างและวางตารางใหม่ซ้ำ ๆ มีข้อ จำกัด ว่าเมื่อใดการแคชนี้อาจเกิดขึ้นได้อย่างไรก็ตามมีความเป็นไปได้ที่จะละเมิด#temp_tablesแต่มีข้อ จำกัด ในการ@table_variablesป้องกันอยู่ดี ค่าใช้จ่ายในการบำรุงรักษาที่แคช#tempตารางเป็นเล็กน้อยมากกว่าสำหรับตัวแปรตารางที่แสดงที่นี่

ข้อมูลเมตาของวัตถุ

สิ่งนี้สำคัญเหมือนกันสำหรับวัตถุทั้งสองประเภท tempdbจะถูกเก็บไว้ในตารางฐานของระบบใน มันตรงไปตรงมามากกว่าที่จะมองหา#tempตารางอย่างไรก็ตาม OBJECT_ID('tempdb..#T')สามารถใช้ในการคีย์ลงในตารางระบบและชื่อที่สร้างขึ้นภายในมีความสัมพันธ์อย่างใกล้ชิดกับชื่อที่กำหนดในCREATE TABLEคำสั่ง สำหรับตัวแปรตารางobject_idฟังก์ชันไม่ทำงานและชื่อภายในเป็นระบบทั้งหมดที่สร้างขึ้นโดยไม่มีความสัมพันธ์กับชื่อตัวแปร ด้านล่างนี้แสดงให้เห็นว่าข้อมูลเมตายังคงอยู่ที่นั่นอย่างไรก็ตามโดยป้อนชื่อคอลัมน์ (หวังว่าจะไม่ซ้ำกัน) สำหรับตารางที่ไม่มีชื่อคอลัมน์ที่ไม่ซ้ำกัน object_id สามารถพิจารณาได้โดยใช้DBCC PAGEตราบเท่าที่ยังไม่ว่างเปล่า

/*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'

เอาท์พุต

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                  |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+

การทำธุรกรรม

การดำเนินงาน@table_variablesจะดำเนินการในฐานะระบบธุรกรรมเป็นอิสระจากการทำธุรกรรมของผู้ใช้ภายนอกใด ๆ ในขณะที่การ#tempดำเนินงานตารางเทียบเท่าจะดำเนินการเป็นส่วนหนึ่งของการทำธุรกรรมของผู้ใช้เอง ด้วยเหตุนี้ROLLBACKคำสั่งจะส่งผลกระทบต่อ#tempตาราง แต่ปล่อยให้@table_variableไม่มีใครแตะต้อง

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

เข้าสู่ระบบ

ทั้งสองสร้างบันทึกการบันทึกไปยังบันทึกการtempdbทำธุรกรรม ความเข้าใจผิดที่พบบ่อยคือนี่ไม่ใช่กรณีของตัวแปรตารางดังนั้นสคริปต์ที่แสดงสิ่งนี้อยู่ด้านล่างมันประกาศตัวแปรตารางเพิ่มสองแถวแล้วอัปเดตและลบออก

เนื่องจากตัวแปรตารางถูกสร้างและลดลงโดยปริยายในตอนเริ่มต้นและตอนท้ายของแบทช์จึงจำเป็นต้องใช้แบตช์หลายชุดเพื่อดูการบันทึกแบบเต็ม

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 

ผลตอบแทน

มุมมองรายละเอียด

สกรีนช็อตของผลลัพธ์

มุมมองสรุป (รวมถึงการบันทึกสำหรับการปล่อยโดยนัยและตารางฐานระบบ)

สกรีนช็อตของผลลัพธ์

เท่าที่ฉันสามารถแยกแยะการดำเนินงานของทั้งสองสร้างการเข้าสู่ระบบจำนวนเท่า ๆ กัน

ขณะที่ปริมาณของการเข้าสู่ระบบจะคล้ายกันมากหนึ่งความแตกต่างที่สำคัญคือบันทึกเข้าสู่ระบบที่เกี่ยวข้องกับ#tempตารางไม่สามารถล้างออกไปจนถึงการทำธุรกรรมของผู้ใช้ใด ๆ ที่มีเสร็จสิ้นเพื่อให้การทำธุรกรรมที่ทำงานมานานแล้วว่าในบางจุดเขียนไปยัง#tempตารางจะป้องกันไม่ให้เข้าสู่ระบบการตัดในtempdbขณะที่การทำธุรกรรมในกำกับของรัฐ วางไข่สำหรับตัวแปรตารางไม่ได้

ตัวแปรของตารางไม่รองรับTRUNCATEดังนั้นจึงอาจเป็นข้อเสียของการบันทึกเมื่อข้อกำหนดคือการลบแถวทั้งหมดออกจากตาราง (แม้ว่าตารางขนาดเล็กมากDELETE สามารถทำงานได้ดีกว่าอยู่ดี )

cardinality

แผนการดำเนินการจำนวนมากที่เกี่ยวข้องกับตัวแปรตารางจะแสดงแถวเดียวโดยประมาณเป็นผลลัพธ์จากพวกเขา การตรวจสอบคุณสมบัติของตัวแปรตารางแสดงว่า SQL Server เชื่อว่าตัวแปรของตารางมีแถวเป็นศูนย์ (เพราะเหตุใดค่าประมาณ 1 แถวจะถูกปล่อยออกมาจากตารางแถวศูนย์จะอธิบายโดย @Paul White ที่นี่ )

อย่างไรก็ตามผลที่แสดงในส่วนก่อนหน้านี้แสดงให้เห็นความถูกต้องนับrows sys.partitionsปัญหาคือในกรณีส่วนใหญ่งบตัวแปรอ้างอิงตารางจะรวบรวมในขณะที่ตารางว่างเปล่า หากคำสั่งนั้นถูกคอมไพล์@table_variableแล้วจะถูกนำมาใช้กับ cardinality ของตารางแทน (สิ่งนี้อาจเกิดขึ้นเนื่องจากความชัดเจนrecompileหรืออาจเป็นเพราะคำสั่งนั้นอ้างอิงวัตถุอื่นที่ทำให้เกิดการคอมไพล์รอการตัดบัญชีหรือคอมไพล์ใหม่)

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

Plan แสดงจำนวนแถวที่ถูกต้องโดยประมาณหลังจากคอมไพล์รอการตัดบัญชี

แสดงจำนวนแถวที่ถูกต้อง

ใน SQL Server 2012 SP2 แนะนำการตั้งค่าสถานะการสืบค้นกลับ 2453 รายละเอียดเพิ่มเติมที่อยู่ภายใต้ "สัมพันธ์เครื่องยนต์" ที่นี่

เมื่อเปิดใช้งานการตั้งค่าสถานะการสืบค้นกลับนี้อาจทำให้คอมไพล์ซ้ำโดยอัตโนมัติพิจารณาบัญชีของ cardinality ที่มีการเปลี่ยนแปลงตามที่กล่าวไว้ในไม่ช้า

หมายเหตุ: เมื่อวันที่ Azure ในระดับความเข้ากัน 150 รวบรวมคำสั่งที่จะรอการตัดบัญชีวันนี้ถึงการดำเนินการครั้งแรก ซึ่งหมายความว่าจะไม่มีปัญหาการประมาณการแถวศูนย์อีกต่อไป

ไม่มีสถิติคอลัมน์

การมีความสำคัญเชิงตารางที่แม่นยำยิ่งขึ้นนั้นไม่ได้หมายความว่าการนับแถวโดยประมาณจะมีความแม่นยำมากขึ้น (เว้นแต่จะทำการดำเนินการกับแถวทั้งหมดในตาราง) SQL Server จะไม่เก็บสถิติคอลัมน์สำหรับตัวแปรตารางเลยดังนั้นจะลดการเดาตามการเปรียบเทียบ (เช่น 10% ของตารางจะถูกส่งกลับสำหรับ=คอลัมน์ที่ไม่ซ้ำกันหรือ 30% สำหรับการ>เปรียบเทียบ) ในทางตรงกันข้ามคอลัมน์สถิติถูกเก็บรักษาไว้สำหรับ#tempตาราง

SQL Server รักษาจำนวนการแก้ไขที่ทำกับแต่ละคอลัมน์ หากจำนวนการแก้ไขตั้งแต่แผนถูกคอมไพล์เกินขีด จำกัด การคอมไพล์ใหม่ (RT) ดังนั้นแผนจะถูกคอมไพล์ใหม่และอัปเดตสถิติ RT ขึ้นอยู่กับประเภทและขนาดของตาราง

จากการแคชแผนใน SQL Server 2008

RT คำนวณดังนี้ (n หมายถึง cardinality ของตารางเมื่อรวบรวมแผนแบบสอบถาม)

ตารางถาวร
- ถ้า n <= 500, RT = 500
- ถ้า n> 500, RT = 500 + 0.20 * n

ตารางชั่วคราว
- ถ้า n <6, RT = 6.
- ถ้า 6 <= n <= 500, RT = 500
- ถ้า n> 500, RT = 500 + 0.20 * n
ตัวแปรตาราง
- RT ไม่มีอยู่ ดังนั้นการคอมไพล์ใหม่จะไม่เกิดขึ้นเนื่องจากการเปลี่ยนแปลงความสำคัญของตัวแปรตาราง (แต่ดูหมายเหตุเกี่ยวกับ TF 2453 ด้านล่าง)

KEEP PLANคำใบ้สามารถใช้ในการตั้งค่า RT สำหรับ#tempตารางเช่นเดียวกับตารางถาวร

ผลกระทบสุทธิของสิ่งนี้คือบ่อยครั้งที่แผนการดำเนินการที่สร้างขึ้นสำหรับ#tempตารางเป็นลำดับของขนาดที่ดีกว่า@table_variablesเมื่อมีหลายแถวที่เกี่ยวข้องเนื่องจาก SQL Server มีข้อมูลที่ดีกว่าในการทำงาน

NB1: ตัวแปรตารางไม่มีสถิติ แต่ยังคงสามารถเกิดเหตุการณ์คอมไพล์ "Statistics Changed" ภายใต้แฟล็กการติดตาม 2453 (ไม่ได้ใช้สำหรับแผน "เล็กน้อย") สิ่งนี้ดูเหมือนจะเกิดขึ้นภายใต้เกณฑ์การคอมไพล์ซ้ำตามที่แสดงสำหรับตารางอุณหภูมิด้านบน N=0 -> RT = 1อีกหนึ่งว่าถ้า เช่นคำสั่งทั้งหมดที่รวบรวมเมื่อตัวแปรตารางว่างเปล่าจะได้รับการคอมไพล์ใหม่และแก้ไขTableCardinalityในครั้งแรกที่ถูกเรียกใช้เมื่อไม่ว่างเปล่า cardinality ตารางเวลาการคอมไพล์จะถูกเก็บไว้ในแผนและหากคำสั่งถูกดำเนินการอีกครั้งด้วย cardinality เดียวกัน (เนื่องจากการไหลของคำสั่งการควบคุมหรือการใช้ซ้ำของแผนแคช) ไม่มีการคอมไพล์ซ้ำเกิดขึ้น

NB2: สำหรับตารางชั่วคราวที่แคชไว้ในขั้นตอนการจัดเก็บเรื่องราวการคอมไพล์ใหม่นั้นซับซ้อนกว่าที่อธิบายไว้ข้างต้น ดูตารางชั่วคราวในขั้นตอนการจัดเก็บสำหรับรายละเอียดเต็มไปด้วยเลือดทั้งหมด

recompiles

เช่นเดียวกับการปรับเปลี่ยนตาม recompiles อธิบายไว้ข้างต้น#tempตารางยังสามารถเชื่อมโยงกับคอมไพล์เพิ่มเติมเพียงเพราะพวกเขาช่วยให้การดำเนินงานที่เป็นสิ่งต้องห้ามสำหรับตัวแปรตารางที่เรียกคอมไพล์ (เช่นการเปลี่ยนแปลง DDL CREATE INDEX, ALTER TABLE)

ล็อค

มันได้รับการระบุว่าตัวแปรตารางไม่ได้มีส่วนร่วมในการล็อค กรณีนี้ไม่ได้. การรันเอาต์พุตด้านล่างไปยังแท็บข้อความ SSMS รายละเอียดของการล็อกที่ถูกใช้และรีลีสสำหรับคำสั่งแทรก

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)

สำหรับการค้นหาที่SELECTมาจากตัวแปรตาราง Paul White ชี้ให้เห็นในความคิดเห็นว่าสิ่งเหล่านี้มาพร้อมกับNOLOCKคำใบ้โดยนัย ดังแสดงด้านล่าง

DECLARE @T TABLE(X INT); 

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

เอาท์พุต

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

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

ผลกระทบของสิ่งนี้ในการล็อคอาจจะค่อนข้างน้อย

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

ไม่ส่งคืนผลลัพธ์เหล่านี้ในลำดับของคีย์ดัชนีซึ่งบ่งชี้ว่า SQL Server ใช้การสแกนตามคำสั่งที่จัดสรรสำหรับทั้งคู่

ฉันรันสคริปต์ด้านบนสองครั้งและผลลัพธ์สำหรับการวิ่งครั้งที่สองต่ำกว่า

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 

ผลลัพธ์การล็อกสำหรับตัวแปรตารางนั้นน้อยมากเนื่องจาก SQL Server เพิ่งได้รับการล็อคความมั่นคงของสคีมาบนวัตถุ แต่สำหรับ#tempตารางมันเกือบจะเบาเหมือนที่ใช้Sล็อคระดับวัตถุ สามารถระบุNOLOCKคำใบ้หรือREAD UNCOMMITTEDระดับการแยกได้อย่างชัดเจนเมื่อทำงานกับ#tempตารางเช่นกัน

ในทำนองเดียวกันกับปัญหาที่เกิดขึ้นกับการบันทึกการทำธุรกรรมของผู้ใช้โดยรอบอาจหมายถึงการล็อคจะถูกเก็บไว้นานกว่าสำหรับ#tempตาราง ด้วยสคริปต์ด้านล่าง

    --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  

เมื่อทำงานด้านนอกของการทำธุรกรรมของผู้ใช้ที่ชัดเจนสำหรับทั้งสองกรณีล็อคเพิ่งกลับมาเมื่อตรวจสอบเป็นล็อคที่ใช้ร่วมกันบนsys.dm_tran_locksDATABASE

ในการยกเลิกการใส่เครื่องหมายความคิดเห็นBEGIN TRAN ... ROLLBACK26 แถวจะถูกส่งกลับแสดงให้เห็นว่าล็อคจะถูกเก็บไว้ทั้งในตัววัตถุและในแถวตารางระบบเพื่อให้สามารถย้อนกลับและป้องกันการทำธุรกรรมอื่น ๆ จากการอ่านข้อมูลปราศจากข้อผูกมัด การดำเนินการตัวแปรตารางที่เทียบเท่านั้นไม่ได้ขึ้นอยู่กับการย้อนกลับของธุรกรรมผู้ใช้และไม่จำเป็นต้องหยุดการล็อกเหล่านี้เพื่อให้เราตรวจสอบในคำสั่งถัดไป แต่การติดตามการล็อคที่ได้รับและเผยแพร่ใน Profiler หรือ เกิดขึ้น

ดัชนี

สำหรับรุ่นก่อนหน้าดัชนี SQL Server 2014 สามารถสร้างได้โดยปริยายบนตัวแปรตารางซึ่งเป็นผลข้างเคียงของการเพิ่มข้อ จำกัด ที่ไม่ซ้ำกันหรือคีย์หลัก แน่นอนว่านี่หมายความว่าสนับสนุนเฉพาะดัชนีเฉพาะเท่านั้น ดัชนีที่ไม่ใช่คลัสเตอร์ที่ไม่ซ้ำกันในตารางที่มีดัชนีคลัสเตอร์ที่ไม่ซ้ำกันสามารถจำลองได้โดยเพียงแค่ประกาศUNIQUE NONCLUSTEREDและเพิ่มคีย์ CI ไปยังจุดสิ้นสุดของคีย์ NCI ที่ต้องการ (SQL Server จะทำสิ่งนี้อยู่เบื้องหลังแม้ว่าจะไม่ซ้ำกันก็ตาม สามารถระบุ NCI ได้)

ดังที่แสดงไว้ก่อนหน้านี้index_optionสามารถระบุ s ต่าง ๆในการประกาศข้อ จำกัด ซึ่งรวมถึงDATA_COMPRESSION, IGNORE_DUP_KEYและ, FILLFACTOR(แม้ว่าจะไม่มีจุดในการตั้งค่าที่มันจะสร้างความแตกต่างในการสร้างดัชนีและคุณไม่สามารถสร้างดัชนีบนตัวแปรตาราง!)

นอกจากนี้ตัวแปรตารางไม่สนับสนุนINCLUDEคอลัมน์ d, ดัชนีที่กรอง (จนถึง 2016) หรือการแบ่งพาร์ติชัน, #tempตารางทำ (ต้องสร้างชุดรูปแบบพาร์ติชันtempdb)

ดัชนีใน SQL Server 2014

ดัชนีที่ไม่ซ้ำกันสามารถประกาศแบบอินไลน์ในนิยามตัวแปรตารางใน SQL Server 2014 ตัวอย่างไวยากรณ์สำหรับสิ่งนี้อยู่ด้านล่าง

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*/
);

ดัชนีใน SQL Server 2016

จาก CTP 3.1 เป็นไปได้ที่จะประกาศดัชนีตัวกรองสำหรับตัวแปรตาราง โดย RTM อาจเป็นกรณีที่อนุญาตให้รวมคอลัมน์ได้แม้ว่าพวกเขาจะไม่ทำให้เป็น SQL16 เนื่องจากข้อ จำกัด ของทรัพยากร

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

ความเท่าเทียม

คำค้นหาที่แทรกลงใน (หรือแก้ไขอย่างอื่น) @table_variablesไม่สามารถมีแผนขนาน#temp_tablesไม่ได้ถูก จำกัด ในลักษณะนี้

มีวิธีแก้ปัญหาที่ชัดเจนในการเขียนใหม่ดังต่อไปนี้จะช่วยให้SELECTส่วนที่จะเกิดขึ้นในแบบคู่ขนาน แต่ที่จบลงด้วยการใช้ตารางชั่วคราวที่ซ่อนอยู่(เบื้องหลัง)

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

ไม่มีข้อ จำกัด ดังกล่าวในแบบสอบถามที่เลือกจากตัวแปรตารางตามที่แสดงในคำตอบของฉันที่นี่

ความแตกต่างการทำงานอื่น ๆ

  • #temp_tablesไม่สามารถใช้ภายในฟังก์ชั่น @table_variablesสามารถใช้ได้ภายใน UDFs แบบสเกลาร์หรือแบบหลายคำสั่ง
  • @table_variables ไม่สามารถมีข้อ จำกัด ในการตั้งชื่อ
  • @table_variablesไม่สามารถSELECT-ed INTO, ALTER-ed, TRUNCATEd หรือเป็นเป้าหมายของDBCCคำสั่งเช่นDBCC CHECKIDENTหรือจากSET IDENTITY INSERTและไม่สนับสนุนคำใบ้ตารางเช่นWITH (FORCESCAN)
  • CHECK ข้อ จำกัด ของตัวแปรตารางไม่ได้รับการพิจารณาโดยเครื่องมือเพิ่มประสิทธิภาพสำหรับการทำให้เข้าใจง่ายสรุปโดยนัยหรือการตรวจจับที่ขัดแย้ง
  • ดูเหมือนว่าตัวแปรตารางจะไม่มีคุณสมบัติสำหรับการเพิ่มประสิทธิภาพการแบ่งปัน rowsetซึ่งหมายความว่าการลบและอัปเดตแผนกับสิ่งเหล่านี้สามารถพบกับค่าใช้จ่ายและการPAGELATCH_EXรอคอยที่มากขึ้น ( ตัวอย่าง )

หน่วยความจำเท่านั้น?

tempdbตามที่ระบุไว้ในตอนต้นทั้งสองได้รับการจัดเก็บไว้บนหน้าเว็บใน อย่างไรก็ตามฉันไม่ได้ระบุว่ามีพฤติกรรมแตกต่างกันหรือไม่เมื่อพูดถึงการเขียนหน้าเหล่านี้ลงดิสก์

ฉันได้ทำการทดสอบเล็กน้อยในตอนนี้และจนถึงขณะนี้ไม่เห็นความแตกต่างดังกล่าว ในการทดสอบเฉพาะที่ฉันทำในอินสแตนซ์ของ SQL Server 250 หน้าของฉันดูเหมือนว่าจะเป็นจุดตัดก่อนที่จะเขียนไฟล์ข้อมูล

หมายเหตุ: พฤติกรรมด้านล่างไม่เกิดขึ้นอีกต่อไปใน SQL Server 2014 หรือSQL Server 2012 SP1 / CU10 หรือ SP2 / CU1นักเขียนที่กระตือรือร้นไม่ได้กระตือรือร้นที่จะเขียนหน้าลงดิสก์ รายละเอียดเพิ่มเติมเกี่ยวกับการเปลี่ยนแปลงว่าในSQL Server 2014: การซ่อน tempdb อัญมณีผลการดำเนินงาน

เรียกใช้สคริปต์ด้านล่าง

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

และการตรวจสอบเขียนไปยังtempdbไฟล์ข้อมูลด้วยการตรวจสอบกระบวนการฉันไม่เห็น (ยกเว้นบางครั้งไปยังหน้าบูตฐานข้อมูลที่ชดเชย 73,728) หลังจากเปลี่ยน250เป็น251ฉันเริ่มเห็นการเขียนดังต่อไปนี้

Procmon

ภาพหน้าจอด้านบนแสดงการเขียน 5 * 32 หน้าและการเขียนหน้าเดียวหนึ่งครั้งระบุว่า 161 หน้าถูกเขียนลงดิสก์ ฉันได้จุดตัด 250 หน้าเหมือนกันเมื่อทดสอบกับตัวแปรตารางด้วย สคริปต์ด้านล่างแสดงวิธีที่แตกต่างโดยดูที่sys.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 

ผล

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

แสดงว่ามีการเขียนหน้า 192 แผ่นลงบนดิสก์และล้างค่าสถานะสกปรก นอกจากนี้ยังแสดงให้เห็นว่าการเขียนลงดิสก์ไม่ได้หมายความว่าหน้าจะถูกขับออกจากบัฟเฟอร์พูลทันที แบบสอบถามที่ตรงกับตัวแปรในตารางนี้ยังคงสามารถตอบสนองได้อย่างสมบูรณ์จากหน่วยความจำ

บนเซิร์ฟเวอร์ที่ไม่ได้ใช้งานที่มีการmax server memoryตั้งค่า2000 MBและDBCC MEMORYSTATUSการรายงานหน้าบัฟเฟอร์พูลจัดสรรเป็นประมาณ 1,843,000 KB (c. 23,000 หน้า) ฉันแทรกลงในตารางข้างต้นด้วยแบทช์ 1,000 แถว / หน้าและสำหรับการบันทึกซ้ำแต่ละครั้ง

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

ทั้งตัวแปรตารางและ#tempตารางให้กราฟเกือบเหมือนกันและจัดการให้พูลบัฟเฟอร์ออกมาสูงสุดก่อนที่จะถึงจุดที่พวกเขาไม่ได้ถูกเก็บไว้ในหน่วยความจำทั้งหมดดังนั้นดูเหมือนจะไม่มีข้อ จำกัด ใด ๆ เกี่ยวกับจำนวนหน่วยความจำ สามารถบริโภคได้

หน้าในบัฟเฟอร์พูล


ฉันพบว่า SQL Server ได้รับ latches มากขึ้นเมื่อสร้าง temp tables (แม้จะมีการแคช) เทียบกับตัวแปร table ที่เทียบเท่า คุณสามารถทดสอบโดยใช้ latch_acquired debug XE และสร้างตารางที่มีคอลัมน์ประมาณ 35 คอลัมน์ ฉันพบตัวแปร table เพื่อใช้ 4 latches และ temp table เพื่อใช้เวลาประมาณ 70 latches
โจ Obbish

40

มีบางสิ่งที่ฉันต้องการชี้ให้เห็นจากประสบการณ์ที่เฉพาะเจาะจงมากกว่าการศึกษา ในฐานะ DBA ฉันใหม่มากดังนั้นโปรดแก้ไขให้ถูกต้องถ้าจำเป็น

  1. #temp ตารางโดยค่าเริ่มต้นใช้การเปรียบเทียบค่าเริ่มต้นของอินสแตนซ์ของ SQL Server ดังนั้นหากไม่ได้ระบุไว้เป็นอย่างอื่นคุณอาจพบปัญหาในการเปรียบเทียบหรืออัปเดตค่าระหว่าง #temp ตารางและตารางฐานข้อมูลหาก masterdb มีการเปรียบเทียบที่แตกต่างจากฐานข้อมูล ดู: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. จากประสบการณ์ส่วนตัวที่สมบูรณ์หน่วยความจำที่มีอยู่ดูเหมือนว่าจะมีผลต่อการทำงานที่ดีขึ้น MSDN แนะนำให้ใช้ตัวแปรตารางเพื่อจัดเก็บชุดผลลัพธ์ที่มีขนาดเล็กลง แต่ส่วนใหญ่แล้วความแตกต่างนั้นยังไม่สามารถสังเกตได้ ในชุดที่มีขนาดใหญ่ขึ้นจะเห็นได้ชัดในบางกรณีว่าตัวแปรตารางนั้นมีหน่วยความจำมากขึ้นและสามารถทำให้คิวรีช้าลงเพื่อรวบรวมข้อมูล

6
นอกจากนี้โปรดทราบว่าการเปรียบเทียบบนตาราง #temp สามารถสืบทอดการเปรียบเทียบของฐานข้อมูลการโทรได้หากคุณใช้ SQL Server 2012 และมีฐานข้อมูลอยู่
Aaron Bertrand

ชี้แจงวันที่ # 2 สำหรับชุดเล็ก vs ชุดใหญ่stackoverflow.com/a/14465163/5224021
GibralterTop
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.