آموزش جامع جدولهای موقت سراسری (##Table) در SQL Server
مقدمه
جدولهای موقت سراسری (##Table) یکی از ابزارهای مهم طراحی پردازش میانی در Microsoft SQL Server است. مسئله اصلی صرفاً نوشتن یک دستور نیست؛ باید بدانیم داده کجا نگهداری میشود، چه کسی آن را میبیند، Optimizer چه اطلاعاتی برای برآورد دارد و پاکسازی در چه زمانی رخ میدهد.
جدول موقت سراسری با پیشوند ## در tempdb ساخته میشود و برخلاف نوع محلی، نشستهای دیگر SQL Server نیز میتوانند آن را ببینند. عمر آن به نشست سازنده و آخرین دستور فعالی که به آن ارجاع دارد وابسته است. همین اشتراکپذیری، هماهنگی نام، مالکیت داده، امنیت و پاکسازی قطعی را به بخش اصلی طراحی تبدیل میکند.
در این راهنما از مثالهای کوچک آغاز میکنیم و سپس به دامنه، NULL، تراکنش، ایندکس، خطاهای رایج و سناریوهای Performance میرسیم. تمام Queryها برای آزمایش در محیط کنترلشده نوشته شدهاند و مثالهای دارای پیشنیاز، آن پیشنیاز را صریح اعلام میکنند.
برای مقایسه این ساختار با گزینههای دیگر، راهنمای جامع اشیای موقت در SQL Server را نیز مطالعه کنید.
تعریف و معماری جدول موقت سراسری
جدول موقت سراسری با پیشوند ## در tempdb ساخته میشود و برخلاف نوع محلی، نشستهای دیگر SQL Server نیز میتوانند آن را ببینند. عمر آن به نشست سازنده و آخرین دستور فعالی که به آن ارجاع دارد وابسته است. همین اشتراکپذیری، هماهنگی نام، مالکیت داده، امنیت و پاکسازی قطعی را به بخش اصلی طراحی تبدیل میکند.
انتخاب صحیح زمانی رخ میدهد که سه محور را همزمان ببینیم: تعداد و توزیع ردیفها، مرز دسترسی نشست یا Batch، و تعداد دفعات خواندن و نوشتن. یک ساختار ساده در حجم کم ممکن است بهترین باشد، اما همان ساختار در گزارش چندمیلیونی میتواند به برآورد Cardinality نامناسب یا فشار منابع منجر شود.
همچنین باید تفاوت میان عمر منطقی داده و عمر فیزیکی شیء را درک کرد. SQL Server بخشی از عملیات را Cache میکند و این موضوع به معنای دائمی شدن داده نیست. قرارداد برنامه باید بر رفتار مستند دامنه و تراکنش تکیه کند، نه بر مشاهده اتفاقی یک اجرای آزمایشی.
Syntax استاندارد
DROP TABLE IF EXISTS ##SharedStage;
CREATE TABLE ##SharedStage
(
SessionID INT NOT NULL,
ItemID INT NOT NULL,
Payload NVARCHAR(200) NULL,
PRIMARY KEY (SessionID, ItemID)
);
پارامترها و اجزای تعریف
- نام با دو علامت ## آغاز میشود و باید در سطح Instance با سایر نشستها برخورد نکند.
- ستون SessionID یا یک کلید اجرای یکتا برای جداسازی داده مصرفکنندگان همزمان توصیه میشود.
- مجوز دسترسی به tempdb و سطح حمله ناشی از نام قابل پیشبینی باید در طراحی امنیت دیده شود.
- پاکسازی باید تحت مالکیت یک فرایند مشخص باشد و به قطع ناگهانی اتصال وابسته نماند.
نوع خروجی و نحوه مصرف
این دستور نیز مقدار اسکالر ندارد و یک شیء جدولی مشترک در tempdb ایجاد میکند. تمام نشستهای مجاز میتوانند با نام منطقی واحد به آن دسترسی یابند، بنابراین قرارداد ستونها و زمان حذف باید میان تولیدکننده و مصرفکننده ثابت باشد.
چه زمانی جدول موقت سراسری را انتخاب کنیم؟
کاربردهای مناسب شامل تبادل کوتاهمدت داده میان Jobها یا نشستهای هماهنگشده در یک Instance، عیبیابی کنترلشدهای که چند اتصال باید یک Snapshot یکسان را مشاهده کنند، مرحلهبندی موقت برای ابزار قدیمی که راه استانداردتری مانند جدول دائمی staging ندارد، سناریوهای مدیریتی محدود که نام، قفل برنامهای و پاکسازی آنها صریح است است. با این حال، مناسب بودن از نام قابلیت نتیجه نمیشود؛ باید Query مصرفکننده و تعداد اجرای همزمان را نیز تحلیل کرد.
- تبادل کوتاهمدت داده میان Jobها یا نشستهای هماهنگشده در یک Instance
- عیبیابی کنترلشدهای که چند اتصال باید یک Snapshot یکسان را مشاهده کنند
- مرحلهبندی موقت برای ابزار قدیمی که راه استانداردتری مانند جدول دائمی staging ندارد
- سناریوهای مدیریتی محدود که نام، قفل برنامهای و پاکسازی آنها صریح است
برای تصمیمگیری، یک نمونه با داده واقعی بسازید، Baseline ثبت کنید و گزینه رقیب را با همان ورودی اجرا کنید. تعداد Logical Read، CPU، Duration و اختلاف Estimated Rows با Actual Rows را مقایسه کنید. اگر SLA همزمانی مهم است، تست بار چندنشستی نیز اجباری است.
مثالهای عملی مستقل
ده سناریوی زیر جنبههای متفاوت جدول موقت سراسری را پوشش میدهند. هر مثال هدف، Query، خروجی مورد انتظار و نکته اجرایی مستقل دارد.
مثال 1: ساخت و خواندن پایه
یک جدول مشترک کوچک میسازیم و داده آن را در همان نشست میخوانیم.
DROP TABLE IF EXISTS ##SharedNumbers;
CREATE TABLE ##SharedNumbers (ID INT PRIMARY KEY, Label NVARCHAR(30));
INSERT INTO ##SharedNumbers VALUES (1,N'یک'),(2,N'دو');
SELECT ID, Label FROM ##SharedNumbers ORDER BY ID;
نکته کاربردی: در محیط واقعی نشست دوم نیز تا زمانی که جدول زنده است میتواند همین SELECT را اجرا کند.
مثال 2: مشاهده نام در tempdb
اطلاعات شیء سراسری را از کاتالوگ tempdb کنترل میکنیم.
DROP TABLE IF EXISTS ##CatalogDemo;
CREATE TABLE ##CatalogDemo (ID INT);
SELECT name, type_desc FROM tempdb.sys.objects WHERE name = N'##CatalogDemo';
| name | type_desc |
|---|
| ##CatalogDemo | USER_TABLE |
نکته کاربردی: کاتالوگ tempdb برای پایش دقیقتر از حدسزدن بر اساس خطای نام است.
مثال 3: جداسازی با شناسه نشست
داده هر اتصال را با @@SPID علامتگذاری و فقط بخش متعلق به همان نشست را میخوانیم.
DROP TABLE IF EXISTS ##SessionStage;
CREATE TABLE ##SessionStage (SessionID INT, ItemID INT, PRIMARY KEY(SessionID,ItemID));
INSERT INTO ##SessionStage VALUES (@@SPID,101),(@@SPID,102);
SELECT ItemID FROM ##SessionStage WHERE SessionID=@@SPID ORDER BY ItemID;
نکته کاربردی: SessionID تداخل داده را کم میکند، اما به تنهایی مجوز دسترسی یا امنیت ردیفی محسوب نمیشود.
مثال 4: کنترل وجود پیش از ساخت
با قفل برنامهای، بخش ساخت شیء مشترک را سریالی میکنیم.
DECLARE @LockResult INT;
EXEC @LockResult=sys.sp_getapplock @Resource=N'##SafeStage_Create',@LockMode=N'Exclusive',@LockOwner=N'Session',@LockTimeout=5000;
IF @LockResult>=0 AND OBJECT_ID('tempdb..##SafeStage') IS NULL CREATE TABLE ##SafeStage(ID INT PRIMARY KEY);
SELECT CASE WHEN OBJECT_ID('tempdb..##SafeStage') IS NOT NULL THEN N'آماده' ELSE N'خطا' END AS StageState;
EXEC sys.sp_releaseapplock @Resource=N'##SafeStage_Create',@LockOwner=N'Session';
نکته کاربردی: sp_getapplock رقابت روی CREATE را مدیریت میکند؛ مسیر خطا و آزادسازی قفل را نیز در کد تولیدی پوشش دهید.
مثال 5: خواندن در Dynamic SQL
نام شیء مشترک در Batch پویا قابل دسترسی است و تعداد ردیفها محاسبه میشود.
DROP TABLE IF EXISTS ##DynamicShared;
CREATE TABLE ##DynamicShared(ID INT);
INSERT INTO ##DynamicShared VALUES(1),(2),(3);
EXEC sys.sp_executesql N'SELECT COUNT(*) AS RowCount FROM ##DynamicShared;';
نکته کاربردی: برای نامهای پویا از QUOTENAME و فهرست مجاز استفاده کنید؛ الحاق ورودی کاربر خطر SQL Injection دارد.
مثال 6: رفتار NULL
Payload اختیاری را ذخیره و وضعیت تکمیل آن را در گزارش مشترک نشان میدهیم.
DROP TABLE IF EXISTS ##Payloads;
CREATE TABLE ##Payloads(ID INT, Payload NVARCHAR(20) NULL);
INSERT INTO ##Payloads VALUES(1,NULL),(2,N'آماده');
SELECT ID, CASE WHEN Payload IS NULL THEN N'ناقص' ELSE N'کامل' END AS StateName FROM ##Payloads ORDER BY ID;
نکته کاربردی: NULL را با IS NULL بررسی کنید؛ مقایسه Payload = NULL هرگز شرط درست تولید نمیکند.
مثال 7: تراکنش و بازگردانی
اثر ROLLBACK بر تغییر داده جدول مشترک را بدون از بین بردن خود جدول نمایش میدهیم.
DROP TABLE IF EXISTS ##TranDemo;
CREATE TABLE ##TranDemo(ID INT);
BEGIN TRANSACTION;
INSERT INTO ##TranDemo VALUES(9);
ROLLBACK TRANSACTION;
SELECT COUNT(*) AS RowCountAfterRollback FROM ##TranDemo;
نکته کاربردی: داده جدول موقت سراسری در تراکنش شرکت میکند؛ اشتراکپذیری به معنای خارج بودن از ACID نیست.
مثال 8: Snapshot گزارش سازمانی
خروجی خلاصه یک اجرای گزارش را با RunID مشترک ثبت میکنیم.
DROP TABLE IF EXISTS ##ReportSnapshot;
CREATE TABLE ##ReportSnapshot(RunID UNIQUEIDENTIFIER, DepartmentID INT, TotalAmount DECIMAL(14,2));
DECLARE @RunID UNIQUEIDENTIFIER='11111111-1111-1111-1111-111111111111';
INSERT INTO ##ReportSnapshot VALUES(@RunID,10,7500.00),(@RunID,20,9200.00);
SELECT DepartmentID,TotalAmount FROM ##ReportSnapshot WHERE RunID=@RunID ORDER BY DepartmentID;
| DepartmentID | TotalAmount |
|---|
| 10 | 7500.00 |
| 20 | 9200.00 |
نکته کاربردی: برای گزارش پایدار و قابل بازیابی، جدول staging دائمی معمولاً از ## مناسبتر است.
مثال 9: رفع برخورد نام
به جای تکیه بر نام عمومی، یک نام شامل SPID را به صورت امن تولید میکنیم.
DECLARE @Name SYSNAME=QUOTENAME(N'##Export_'+CONVERT(NVARCHAR(12),@@SPID));
DECLARE @Sql NVARCHAR(MAX)=N'CREATE TABLE '+@Name+N'(ID INT); INSERT INTO '+@Name+N' VALUES(1); SELECT COUNT(*) AS RowCount FROM '+@Name+N'; DROP TABLE '+@Name+N';';
EXEC sys.sp_executesql @Sql;
نکته کاربردی: QUOTENAME فقط نام شیء را ایمن میکند؛ مقادیر داده باید همچنان پارامتری ارسال شوند.
مثال 10: ایندکس و پاکسازی قطعی
برای فیلتر RunID ایندکس میسازیم و پس از مصرف، جدول را صریح حذف میکنیم.
DROP TABLE IF EXISTS ##IndexedStage;
CREATE TABLE ##IndexedStage(RunID INT, ItemID INT, Payload NVARCHAR(50));
CREATE CLUSTERED INDEX CX_IndexedStage ON ##IndexedStage(RunID,ItemID);
INSERT INTO ##IndexedStage VALUES(7,1,N'A'),(7,2,N'B'),(8,1,N'C');
SELECT ItemID,Payload FROM ##IndexedStage WHERE RunID=7 ORDER BY ItemID;
DROP TABLE ##IndexedStage;
نکته کاربردی: حذف صریح مسئولیت عمر شیء را روشن میکند و ایندکس ترکیبی دسترسی هر اجرا را هدفمند میسازد.
خطاهای رایج و روش اصلاح
خطای 1
نام ثابت ## باعث برخورد دو اجرای همزمان میشود و ممکن است یک اجرا داده اجرای دیگر را حذف یا بازنویسی کند. راه اصلاح این است که فرض را به یک آزمون قابل تکرار تبدیل کنید و رفتار را در نسخه و Compatibility Level مقصد بسنجید.
خطای 2
اعتماد به حذف خودکار بدون ثبت مالک و زمان انقضا، اشیای رهاشده و رفتار مسابقهای ایجاد میکند. راه اصلاح این است که فرض را به یک آزمون قابل تکرار تبدیل کنید و رفتار را در نسخه و Compatibility Level مقصد بسنجید.
خطای 3
قرار دادن داده حساس در شیء مشترک بدون کنترل مجوز و کلید جداسازی، مرز محرمانگی نشستها را از بین میبرد. راه اصلاح این است که فرض را به یک آزمون قابل تکرار تبدیل کنید و رفتار را در نسخه و Compatibility Level مقصد بسنجید.
خطای 4
استفاده از ## بهعنوان صف یا حافظه پایدار، دوام و قابلیت بازیابی لازم را فراهم نمیکند. راه اصلاح این است که فرض را به یک آزمون قابل تکرار تبدیل کنید و رفتار را در نسخه و Compatibility Level مقصد بسنجید.
ملاحظات کارایی و بهینهسازی
جدول سراسری همان هزینههای تخصیص، ثبت و I/O مربوط به tempdb را دارد و جهانی بودن آن رایگان نیست.
کلید اجرای یکتا و ایندکس مناسب، هم تداخل منطقی را کاهش میدهد و هم خواندن هر مصرفکننده را محدود میکند.
برای هماهنگی ساخت و حذف میتوان از sp_getapplock استفاده کرد تا رقابت روی نام به خطای تصادفی تبدیل نشود.
در بار سازمانی، جدول staging دائمی با ستون RunID، امنیت روشن و Job پاکسازی اغلب قابل پشتیبانیتر است.
زمان انتظار، رشد tempdb و قفلهای schema را با Extended Events و DMVها اندازه بگیرید و صرفاً به سریع بودن تست تکنشستی تکیه نکنید.
هیچ Hint یا نوع شیئی درمان همگانی نیست. Baseline را نگه دارید تا پس از تغییر بتوانید رگرسیون را تشخیص دهید. برای Queryهای حساس، Query Store و Actual Plan امکان مقایسه پایدارتر را فراهم میکنند و Wait Statistics نشان میدهد مشکل واقعاً CPU، I/O، قفل یا تخصیص است.
بهترین روشها
- Schema جدول موقت سراسری را باریک و صریح تعریف کنید و از ستونهای بلااستفاده دوری کنید.
- طول عمر، مالک ایجاد و مسئول پاکسازی را در طراحی و مستندات مشخص کنید.
- ایندکس را از روی Predicate، Join و Order By واقعی طراحی کنید، نه از روی حدس.
- داده حساس را متناسب با دامنه دید و مجوزها محافظت کنید.
- مسیر موفق، خطا، Rollback و اجرای همزمان را در تست خودکار پوشش دهید.
- پس از Upgrade یا تغییر Compatibility Level آزمون Performance را تکرار کنید.
- کد DDL و Query را در Source Control نگه دارید و تغییر را با برنامه بازگشت منتشر کنید.
سؤالات متداول
1. جدول موقت سراسری دقیقاً چیست و چه مسئلهای را حل میکند؟
جدول موقت سراسری با پیشوند ## در tempdb ساخته میشود و برخلاف نوع محلی، نشستهای دیگر SQL Server نیز میتوانند آن را ببینند. عمر آن به نشست سازنده و آخرین دستور فعالی که به آن ارجاع دارد وابسته است. همین اشتراکپذیری، هماهنگی نام، مالکیت داده، امنیت و پاکسازی قطعی را به بخش اصلی طراحی تبدیل میکند. انتخاب آن باید از نیاز واقعی دامنه، حجم داده و نحوه مصرف شروع شود.
2. برای شروع کار با جدول موقت سراسری چه مراحلی لازم است؟
ابتدا Schema و طول عمر داده را مشخص کنید، سپس نمونه Syntax مقاله را در دیتابیس آزمایشی اجرا کنید و با داده شبیه تولید، صحت و طرح اجرا را بسنجید.
3. آیا استفاده از جدول موقت سراسری هزینه توسعه گزارش را کاهش میدهد؟
اگر ساختار برای چند مرحله پردازش مناسب باشد، کد سادهتر و عیبیابی سریعتر میشود. در پروژه تجاری بهتر است هزینه tempdb، نگهداری و همزمانی نیز در برآورد مشاوره لحاظ شود.
4. چه زمانی سرمایهگذاری روی بهینهسازی جدول موقت سراسری توجیه تجاری دارد؟
وقتی زمان پاسخ، مصرف CPU یا انتظار کاربران روی درآمد و SLA اثر دارد، ثبت Baseline و آزمایش کنترلشده میتواند ارزش تغییر را روشن کند؛ بهینهسازی بدون عدد قابل دفاع نیست.
5. جدول موقت سراسری چه تفاوتی با سایر اشیای موقت دارد؟
تفاوت اصلی در دامنه دید، طول عمر، آمار، ایندکس، مدل ذخیرهسازی و هزینه همزمانی است. جدول مقایسه مقاله مادر انتخاب میان #Table، ##Table، @Table و In-Memory را خلاصه میکند.
6. آیا میتوان طراحی جدول موقت سراسری را برای پروژه سازمانی سفارش داد؟
بله؛ در یک خدمت حرفهای، الگوی Query، حجم، Execution Plan، Wait Statistics و محدودیت نسخه بررسی میشود و اسکریپت مهاجرت و آزمون بازگشت نیز تحویل میگردد.
7. رایجترین خطای جدول موقت سراسری چیست؟
نام ثابت ## باعث برخورد دو اجرای همزمان میشود و ممکن است یک اجرا داده اجرای دیگر را حذف یا بازنویسی کند. برای جلوگیری، مسیر خطا را مانند مسیر موفقیت در تست خودکار پوشش دهید.
8. چگونه Performance جدول موقت سراسری را اندازه بگیریم؟
STATISTICS IO، STATISTICS TIME، Actual Execution Plan، مصرف tempdb یا XTP و زمان صدکی را قبل و بعد ثبت کنید. چند اجرای همزمان از تست تککاربره معتبرتر است.
9. بهترین روش استفاده از جدول موقت سراسری چیست؟
Schema باریک، نام روشن، مالکیت طول عمر، پاکسازی مشخص و ایندکس مبتنی بر Query واقعی اصول پایهاند. تصمیم نهایی باید با داده تولیدی و طرح اجرای واقعی تأیید شود.
10. جدول موقت سراسری با کدام نسخههای SQL Server سازگار است؟
اصل قابلیت در نسخههای پشتیبانیشده SQL Server موجود است، اما جزئیات بهبودهای Optimizer و In-Memory با نسخه و Compatibility Level فرق دارد. مستندات نسخه مقصد و آزمون رگرسیون را معیار قرار دهید.
سؤالات مصاحبه تخصصی
پرسش 1: دامنه و طول عمر جدول موقت سراسری را توضیح دهید و یک مورد استفاده مناسب نام ببرید.
پاسخ حرفهای باید علاوه بر تعریف، یک Trade-off و روش اندازهگیری ارائه کند. نام بردن از Actual Plan، IO، CPU، دامنه و آزمون همزمانی نشان میدهد داوطلب قابلیت را در پروژه واقعی فهمیده است.
پرسش 2: Optimizer برای جدول موقت سراسری چه اطلاعاتی در اختیار دارد و این موضوع چگونه بر Join اثر میگذارد؟
پاسخ حرفهای باید علاوه بر تعریف، یک Trade-off و روش اندازهگیری ارائه کند. نام بردن از Actual Plan، IO، CPU، دامنه و آزمون همزمانی نشان میدهد داوطلب قابلیت را در پروژه واقعی فهمیده است.
پرسش 3: اگر تعداد ردیفها صد برابر شود، چه شاخصهایی را پیش از تغییر طراحی بررسی میکنید؟
پاسخ حرفهای باید علاوه بر تعریف، یک Trade-off و روش اندازهگیری ارائه کند. نام بردن از Actual Plan، IO، CPU، دامنه و آزمون همزمانی نشان میدهد داوطلب قابلیت را در پروژه واقعی فهمیده است.
پرسش 4: نقش Index و هزینه نگهداری آن در این ساختار چیست؟
پاسخ حرفهای باید علاوه بر تعریف، یک Trade-off و روش اندازهگیری ارائه کند. نام بردن از Actual Plan، IO، CPU، دامنه و آزمون همزمانی نشان میدهد داوطلب قابلیت را در پروژه واقعی فهمیده است.
پرسش 5: چگونه Race Condition، پاکسازی و مسیر Rollback را آزمایش میکنید؟
پاسخ حرفهای باید علاوه بر تعریف، یک Trade-off و روش اندازهگیری ارائه کند. نام بردن از Actual Plan، IO، CPU، دامنه و آزمون همزمانی نشان میدهد داوطلب قابلیت را در پروژه واقعی فهمیده است.
پرسش 6: برای مهاجرت به گزینه دیگر چه Baseline و معیار پذیرشی تعریف میکنید؟
پاسخ حرفهای باید علاوه بر تعریف، یک Trade-off و روش اندازهگیری ارائه کند. نام بردن از Actual Plan، IO، CPU، دامنه و آزمون همزمانی نشان میدهد داوطلب قابلیت را در پروژه واقعی فهمیده است.
چکلیست نهایی
- هدف داده میانی و طول عمر آن مشخص است.
- حجم معمول، اوج و تعداد نشست همزمان اندازهگیری شده است.
- Schema و نوع داده بیش از نیاز بزرگ نیست.
- Predicate و Joinهای اصلی ایندکس مناسب دارند.
- Estimated Rows و Actual Rows مقایسه شدهاند.
- مسیر NULL، خطا و Rollback تست شده است.
- پاکسازی و مالکیت شیء روشن است.
- Baseline و برنامه بازگشت ثبت شده است.
جمعبندی
جدولهای موقت سراسری (##Table) زمانی ارزشمند است که ویژگیهای واقعی آن با مسئله هماهنگ باشد. دامنه، حجم، آمار، ایندکس و همزمانی را یک تصمیم واحد ببینید و به یک آزمایش سریع تکنشستی اکتفا نکنید.
از مثالهای این مقاله بهعنوان نقطه شروع استفاده کنید، سپس داده و Query پروژه خود را جایگزین کنید. نتیجه درست باید هم از نظر صحت کسبوکار و هم از نظر معیارهای Performance قابل دفاع باشد.
برای مرور همه گزینهها و انتخاب ساختار مناسب، به مقاله مادر اشیای موقت در SQL Server بازگردید.