جایگزینی برای راهنمای پرخطر NOLOCK در SQL Server
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۶/۰۲ ۰۹:۰۵
آدرس: www.dntips.ir
SELECT بهصورت پیشفرض قفلهای ریزدانه در سطح ردیف (Row) یا صفحه (Page) میگیرد و پس از خواندن بلافاصله آنها را آزاد میکند. قفل شدن کل جدول تنها زمانی رخ میدهد که پدیده ترفیع قفل (Lock Escalation) فعال شود؛ یعنی زمانی که حجم ردیفهای درگیر از یک آستانه (معمولاً حدود ۵۰۰۰ قفل خرد در SQL Server) عبور کند و سیستم برای جلوگیری از پر شدن حافظه، قفلها را به سطح جدول ارتقا دهد.WITH (NOLOCK) بهترین شروع برای تمام کوئریها است.NOLOCK سطح جداسازی READ UNCOMMITTED را فعال میکند و خطرات جدی به همراه دارد:X-Lock) اعمال میکنند.ALTER TABLE ... LOCK=NONE برای کوئریهای SELECT است.| پایگاه داده | مکانیزم پیشفرض خواندن | آیا نویسنده خواننده را مسدود میکند؟ | راهکار استاندارد خواندن بدون بلاک |
| PostgreSQL | MVCC ذاتی | خیر | رفتار پیشفرض بدون نیاز به تنظیم خاص |
| MySQL (InnoDB) | MVCC ذاتی (Undo Logs) | خیر | رفتار پیشفرض بدون نیاز به تنظیم خاص |
| SQL Server | Locking بدبینانه | بله (در سطح پیشفرض) | فعالسازی RCSI در سطح دیتابیس |
NOLOCK در SQL Server، فعالسازی Read Committed Snapshot Isolation (RCSI) است. اینکار رفتار سطح جداسازی پیشفرض SQL Server یعنی READ COMMITTED را از حالت مبتنی بر قفل بدبینانه (Pessimistic Locking) به حالت مبتنی بر نسخهبندی ردیفها (Row Versioning / Optimistic) تغییر میدهد. با فعالسازی این قابلیت، کوئریهای خواندن (SELECT) دیگر قفل اشتراکی (S-Lock) نگرفته و منتظر پایان تراکنشهای نوشتن نمیمانند؛ در عوض، آخرین نسخه تاییدشده (Committed) رکوردها را از فضای tempdb میخوانند.tempdbو I/O دیسک (بزرگترین ریسک)UPDATE یا DELETE)، نسخه قدیمی آن در Version Store واقع در tempdb ذخیره میشود.tempdb روی درایوهای بسیار سریع (NVMe SSD) قرار دارد، چندین دیتافایل هماندازه برای آن ایجاد شده و فضای خالی کافی دارد.WITH (UPDLOCK) استفاده شده باشد.SELECT name, is_read_committed_snapshot_on, snapshot_isolation_state_desc FROM sys.databases WHERE name = 'YourDatabaseName';
is_read_committed_snapshot_on برابر با 0 باشد، غیرفعال است.ROLLBACK IMMEDIATE استفاده کنید (بهتر است در ساعات کمترافیک انجام شود):USE master; GO ALTER DATABASE YourDatabaseName SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO
ROLLBACK IMMEDIATE تمام تراکنشهای باز جاری را لغو و اتصالات را قطع میکند تا قفل لازم بلافاصله به دست آید).1 تغییر یافته است:SELECT name, is_read_committed_snapshot_on FROM sys.databases WHERE name = 'YourDatabaseName';
tempdb و استفاده از Version Store را پایش کنید:-- بررسی میزان فضای مصرفی Version Store در tempdb (بر حسب مگابایت)
SELECT
SUM(version_store_reserved_page_count) * 8 / 1024.0 AS VersionStore_MB
FROM sys.dm_db_file_space_usage;
-- شناسایی تراکنشهایی که بیشترین حجم نسخه را در tempdb باز نگه داشتهاند
SELECT
at.transaction_id,
at.name,
at.transaction_begin_time,
DATEDIFF(minute, at.transaction_begin_time, GETDATE()) AS active_minutes
FROM sys.dm_tran_active_snapshot_database_transactions ast
JOIN sys.dm_tran_active_transactions at ON ast.transaction_id = at.transaction_id
ORDER BY ast.elapsed_time_seconds DESC;ALLOW_SNAPSHOT_ISOLATION)| ویژگی | READ_COMMITTED_SNAPSHOT (RCSI) | ALLOW_SNAPSHOT_ISOLATION (SI) |
| تغییر کد برنامه | خیر (سطح پیشفرض READ COMMITTED ارتقا مییابد) | بله (باید قبل از کوئری SET TRANSACTION ISOLATION LEVEL SNAPSHOT صدا زده شود) |
| سطح پایداری (Statement vs Transaction) | در سطح هر Statement آخرین نسخه خوانده میشود | در سطح کل Transaction نسخه از زمان شروع تراکنش خوانده میشود |
| خطای Update Conflict | خیر (پشت قفل نوشتن منتظر میماند) | بله (اگر دو تراکنش همزمان یک سطر را تغییر دهند خطای ۳۹۶۰ رخ میدهد) |
BEGIN TRAN ... COMMIT فقط دستورات ضروری نوشتن را قرار دهید و منطق پردازشی، فراخوانی وبسرویسها یا محاسبات طولانی را خارج از تراکنش انجام دهید.