it-swarm.asia

ما الفرق بين جدول مؤقت ومتغير جدول في SQL Server؟

يبدو أن هذه منطقة بها عدد قليل من الأساطير والآراء المتضاربة.

إذن ما هو الفرق بين متغير جدول وجدول مؤقت محلي في SQL Server؟

459
Martin Smith

المحتويات

Contents

تحذير

يناقش هذا الجواب متغيرات الجدول "الكلاسيكية" المقدمة في 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 يتم تخزين الصفين)

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

الموقع المنطقي

تتصرف @table_variables كما لو كانت جزءًا من قاعدة البيانات الحالية أكثر من الجداول #temp. بالنسبة إلى متغيرات الجدول (منذ 2005) ، فإن عمليات ترتيب الأعمدة إذا لم يتم تحديدها بشكل صريح ستكون قاعدة البيانات الحالية بينما بالنسبة لجداول #temp ، فستستخدم الترتيب الافتراضي لـ tempdb ( المزيد من التفاصيل ). بالإضافة إلى ذلك ، يجب أن تكون أنواع البيانات المعرفة ومجموعات XML في tempdb لاستخدامها في جداول #temp ولكن يمكن لمتغيرات الجدول استخدامها من قاعدة البيانات الحالية ( المصدر ).

يقدم SQL Server 2012 قواعد البيانات المضمنة. يختلف سلوك الجداول المؤقتة في هذه (h/t Aaron)

يتم تجميع بيانات الجدول المؤقت في قاعدة البيانات المضمنة في ترتيب قاعدة البيانات الواردة.

  • ستكون جميع البيانات الوصفية المرتبطة بالجداول المؤقتة (على سبيل المثال ، أسماء الجداول والأعمدة والفهارس وما إلى ذلك) في ترتيب الكتالوج.
  • لا يجوز استخدام القيود المسماة في الجداول المؤقتة.
  • قد لا تشير الجداول المؤقتة إلى الأنواع المعرفة من قبل المستخدم أو مجموعات مخطط XML أو الوظائف المعرفة من قبل المستخدم.

الرؤية لمختلف المجالات

يمكن الوصول إلى @table_variables فقط داخل الدفعة والنطاق الذي تم فيه الإعلان عنها. يمكن الوصول إلى #temp_tables ضمن مجموعات فرعية (مشغلات متداخلة ، إجراء ، exec مكالمات). #temp_tables الذي تم إنشاؤه على النطاق الخارجي (@@NESTLEVEL=0) يمكن أن يمتد على دفعات أيضًا لأنها تستمر حتى تنتهي الجلسة. لا يمكن إنشاء أي نوع من الكائنات في مجموعة فرعية والوصول إليها في نطاق الاستدعاء ولكن كما هو موضح أدناه (جداول ##temp العمومية يمكن على الرغم من ذلك).

العمر

يتم إنشاء @table_variables بشكل ضمني عند تنفيذ مجموعة تحتوي على عبارة DECLARE @.. TABLE (قبل تشغيل أي رمز مستخدم في هذه الدفعة) وإسقاطها ضمنيًا في النهاية.

على الرغم من أن المحلل اللغوي لن يسمح لك بمحاولة استخدام متغير الجدول قبل العبارة DECLARE ، يمكن رؤية الإنشاء الضمني أدناه.

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

--Works fine
SELECT *
FROM @T

يتم إنشاء #temp_tables بشكل صريح عند مواجهة عبارة TSQL CREATE TABLE ويمكن إسقاطها بشكل صريح باستخدام 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 

عائدات

عرض تفصيلي

Screenshot of results

عرض ملخص (يتضمن تسجيل الدخول لإسقاط ضمني وجداول قاعدة النظام)

Screenshot of results

بقدر ما كنت قادرًا على تمييز العمليات على كلاهما تولد كميات متساوية تقريبًا من التسجيل.

في حين أن = الكمية من التسجيل متشابه جدًا ، هناك اختلاف مهم واحد هو أنه لا يمكن مسح سجلات السجلات ذات الصلة بجداول #temp حتى تنتهي أي معاملة مستخدم تحتوي على ذلك بحيث تكون المعاملة طويلة الأمد التي في وقت ما يكتب إلى الجداول #temp سيمنع اقتطاع السجل في tempdb في حين أن المعاملات المستقلة الناتجة عن متغيرات الجدول لا تفعل ذلك.

لا تدعم متغيرات الجدول TRUNCATE لذا يمكن أن تكون في وضع غير صالح لتسجيل الدخول عندما يكون المتطلب هو إزالة جميع الصفوف من الجدول (على الرغم من أن الجداول الصغيرة جدًا DELETEيمكن أن تعمل أفضل على أي حال )

أصالة

ستظهر العديد من خطط التنفيذ التي تتضمن متغيرات الجدول صفًا واحدًا تقديريًا كمخرجات منها. يوضح فحص خصائص متغير الجدول أن SQL Server يعتقد أن متغير الجدول يحتوي على صفر صفوف (لماذا يقدر أن صفًا واحدًا سينبعث من جدول صف صف موضح بواسطةPaul White هنا ).

ومع ذلك ، فإن النتائج الموضحة في القسم السابق تُظهر عدد rows دقيقًا في sys.partitions. المشكلة هي أنه في معظم المناسبات يتم تجميع العبارات التي تشير إلى متغيرات الجدول بينما يكون الجدول فارغًا. إذا تم تجميع (إعادة) العبارة بعد تعبئة @table_variable ، فسيتم استخدام ذلك لكتابة الجدول بدلاً من ذلك (قد يحدث هذا بسبب 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

تظهر الخطة عدد الصفوف المقدرة الدقيقة بعد الترجمة المؤجلة.

Shows accurate row count

في SQL Server 2012 SP2 ، يتم تقديم إشارة التتبع 2453. مزيد من التفاصيل تحت "Relational Engine" هنا .

عندما يتم تمكين علامة التتبع هذه ، يمكن أن تتسبب في إعادة الترجمات التلقائية إلى مراعاة الكاردينال المتغير على النحو الذي تمت مناقشته في وقت قصير جدًا.

ملاحظة: على Azure في مستوى التوافق ، تم تأجيل 150 من البيان حتى التنفيذ الأول . هذا يعني أنها لن تخضع بعد الآن لمشكلة تقدير صف الصفر.

لا توجد إحصائيات عمود

لا يعني امتلاك أصالة أدق في الجدول أن عدد الصفوف المقدر سيكون أكثر دقة (إلا إذا أجريت عملية على جميع الصفوف في الجدول). لا يحتفظ SQL Server بإحصائيات الأعمدة لمتغيرات الجدول على الإطلاق ، لذا سوف يعود إلى التخمينات بناءً على مسند المقارنة (على سبيل المثال ، سيتم إرجاع 10٪ من الجدول لـ = مقابل عمود غير فريد أو 30٪ للمقارنة >). في إحصائيات العمود المقابل يتم الاحتفاظ لجداول #temp.

يحتفظ SQL Server بعدد من التعديلات التي تم إجراؤها على كل عمود. إذا تجاوز عدد التعديلات منذ تجميع الخطة عتبة إعادة الترجمة (RT) ، فسيتم إعادة تجميع الخطة وتحديث الإحصائيات. RT يعتمد على نوع الجدول وحجمه.

من التخزين المؤقت للخطة في SQL Server 2008

يتم حساب RT على النحو التالي. (يشير n إلى أصل الجدول عند تجميع خطة الاستعلام.)

جدول دائم
- إذا كان 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: لا تحتوي متغيرات الجدول على إحصائيات ولكن لا يزال بإمكانها تحمل حدث إعادة ترجمة "تم تغيير الإحصائيات" تحت إشارة التتبع 2453 (لا ينطبق على الخطط "التافهة") يبدو أن هذا يحدث تحت نفس عتبات إعادة التحويل كما هو موضح للجداول المؤقتة أعلاه مع واحد إضافي إذا N=0 -> RT = 1. بمعنى أن جميع العبارات التي تم تجميعها عندما يكون متغير الجدول فارغًا سينتهي بها الأمر أن يتم إعادة تجميعها وتصحيحها TableCardinality في المرة الأولى التي يتم تنفيذها فيها عندما تكون فارغة. يتم تخزين أصالة الجدول الزمني للترجمة في الخطة ، وإذا تم تنفيذ العبارة مرة أخرى بنفس الكاردينال (إما بسبب تدفق عبارات التحكم أو إعادة استخدام خطة مخزنة مؤقتًا) فلن يحدث إعادة ترجمة.

ملحوظة 2: بالنسبة للجداول المؤقتة المخزنة مؤقتًا في الإجراءات المخزنة ، فإن قصة إعادة التركيب أكثر تعقيدًا بكثير مما هو موضح أعلاه. انظر الجداول المؤقتة في الإجراءات المخزنة لجميع تفاصيل الدموية.

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_locks هو قفل مشترك على DATABASE.

عند إلغاء تثبيت BEGIN TRAN ... ROLLBACK ، يتم إرجاع 26 صفًا لتوضيح أن الأقفال محفوظة على كل من الكائن نفسه وعلى صفوف جدول النظام للسماح بالتراجع ومنع المعاملات الأخرى من قراءة البيانات غير الملتزم بها. لا تخضع عملية متغير الجدول المكافئ للتراجع عن معاملة المستخدم ولا تحتاج إلى الاحتفاظ بهذه الأقفال لنا لإيداع الحساب التالي ولكن أقفال التتبع التي تم الحصول عليها وإصدارها في Profiler أو باستخدام إشارة التتبع 1200 تظهر الكثير من أحداث القفل لا تزال تحدث.

الفهارس

بالنسبة للإصدارات السابقة لـ SQL Server 2014 ، لا يمكن إنشاء فهارس ضمنيًا إلا على متغيرات الجدول كأثر جانبي لإضافة قيد فريد أو مفتاح أساسي. هذا يعني بالطبع أنه يتم دعم الفهارس الفريدة فقط. يمكن محاكاة الفهرس غير الفريد غير المتكتل على جدول به فهرس فريد متفاوت المسافات ، ولكن ببساطة عن طريق الإعلان عنه UNIQUE NONCLUSTERED وإضافة مفتاح CI إلى نهاية مفتاح NCI المطلوب (SQL Server قم بذلك خلف الكواليس على أي حال حتى إذا كان من الممكن تحديد NCI غير فريد)

كما هو موضح سابقًا ، يمكن تحديد العديد من index_option في تصريح القيد بما في ذلك 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 it قد تكون الحالة التي يُسمح فيها بالأعمدة المضمنة أيضًا ، على الرغم من أنها لن تدخل على الأرجح إلى 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 - تحرير INTO ، ALTER - تحرير ، TRUNCATE د أو أن يكون هدف DBCC أوامر مثل DBCC CHECKIDENT أو SET IDENTITY INSERT ولا تدعم تلميحات الجدول مثل WITH (FORCESCAN)
  • CHECK لا يعتبر المحسن قيودًا على متغيرات الجدول للتبسيط أو المسندات الضمنية أو الكشف عن التناقض.
  • لا يبدو أن متغيرات الجدول مؤهلة للحصول على تحسين مشاركة مجموعة الصفوف مما يعني أن حذف وتحديث الخطط مقابل هذه يمكن أن تواجه المزيد من النفقات العامة و PAGELATCH_EX ينتظر. ( مثال )

ذاكرة فقط؟

كما هو مذكور في البداية ، يتم تخزين كلاهما على صفحات في tempdb. ومع ذلك ، لم أتطرق إلى ما إذا كان هناك أي اختلاف في السلوك عندما يتعلق الأمر بكتابة هذه الصفحات على القرص.

لقد أجريت القليل من الاختبارات على هذا الآن وحتى الآن لم أر مثل هذا الاختلاف. في الاختبار المحدد الذي أجريته على مثيلي من صفحات SQL Server 250 ، تبدو نقطة التوقف قبل كتابة ملف البيانات.

ملاحظة: لم يعد السلوك أدناه يحدث في SQL Server 2014 أو SQL Server 2012 SP1/CU10 أو SP2/CU1 لم يعد الكاتب المتحمس حريصًا على كتابة الصفحات على القرص. مزيد من التفاصيل حول هذا التغيير على SQL Server 2014: tempdb Hidden Performance Gem .

تشغيل البرنامج النصي أدناه

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 باستخدام Process Monitor ، لم أر أي شيء (باستثناء أحيانًا إلى صفحة تمهيد قاعدة البيانات عند الإزاحة 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 كيلوبايت (حوالي 23،000 صفحة) قمت بإدراجها في الجداول أعلاه في دفعات من 1000 صف/صفحة ولكل تكرار تم تسجيله.

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

أعطى كل من متغير الجدول والجدول #temp رسومًا بيانية متطابقة تقريبًا وتمكنوا من الوصول إلى حد كبير خارج مجموعة المخزن المؤقت قبل الوصول إلى النقطة التي لم يتم الاحتفاظ بها بالكامل في الذاكرة لذا لا يبدو أن هناك أي قيود خاصة على مقدار الذاكرة التي يمكن أن تستهلكها.

Pages in Buffer Pool

682
Martin Smith

هناك بعض الأشياء التي أود أن أشير إليها بناءً على تجارب معينة أكثر من الدراسة. بصفتي DBA ، أنا جديد جدًا ، لذا يُرجى تصحيح لي عند اللزوم.

  1. تستخدم الجداول #temp بشكل افتراضي الترتيب الافتراضي لمثيل SQL Server. لذلك ما لم يتم تحديد خلاف ذلك ، قد تواجه مشاكل في مقارنة أو تحديث القيم بين #temp والجداول في قاعدة البيانات ، إذا كان لدى masterdb ترتيب مختلف عن قاعدة البيانات. انظر: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. بناءً على الخبرة الشخصية تمامًا ، يبدو أن الذاكرة المتوفرة لها تأثير على أدائها بشكل أفضل. توصي MSDN باستخدام متغيرات الجدول لتخزين مجموعات نتائج أصغر ، ولكن في معظم الأحيان لا يكون الفرق ملحوظًا. ومع ذلك ، في مجموعات أكبر ، أصبح من الواضح في بعض الحالات أن متغيرات الجدول تستهلك قدرًا أكبر بكثير من الذاكرة ويمكن أن تبطئ الاستعلام إلى زحف.
41
Kahn