مثالهای عملی و قابل اجرا
مثال 1: خواندن شمارنده تجمعی PAGEIOLATCH_SH
اولین گام این است که وجود و اندازه تاریخی PAGEIOLATCH_SH را ببینید. ستون wait_time_ms شامل signal_wait_time_ms نیز هست و مقدار از زمان Startup یا آخرین پاکسازی Wait Stats تجمع یافته است.
SELECT
wait_type,
waiting_tasks_count,
wait_time_ms,
signal_wait_time_ms,
CAST(wait_time_ms * 1.0 /
NULLIF(waiting_tasks_count, 0) AS decimal(18,2)) AS avg_wait_ms
FROM sys.dm_os_wait_stats
WHERE wait_type = N'PAGEIOLATCH_SH';
| Wait Type | تعداد | زمان کل | میانگین |
|---|
| PAGEIOLATCH_SH | 12,480 | 3,842,000 ms | 307.85 ms |
این خروجی Baseline است و رخداد جاری را اثبات نمیکند. زمان Startup، workload و Delta بعدی را ثبت کنید تا درباره اهمیت PAGEIOLATCH_SH نتیجهگیری شود.
مثال 2: محاسبه Delta پنجثانیهای PAGEIOLATCH_SH
دو Snapshot کوتاه نشان میدهد در بازه مشاهده چه مقدار انتظار جدید ساخته شده است. برای محیط واقعی، بازه را با چرخه کاری سامانه هماهنگ کنید و Snapshotها را در جدول مانیتورینگ پایدار نگه دارید.
IF OBJECT_ID(N'tempdb..#WaitStart') IS NOT NULL
DROP TABLE #WaitStart;
SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms
INTO #WaitStart
FROM sys.dm_os_wait_stats
WHERE wait_type = N'PAGEIOLATCH_SH';
WAITFOR DELAY '00:00:05';
SELECT
w.wait_type,
w.waiting_tasks_count - ISNULL(b.waiting_tasks_count, 0) AS delta_tasks,
w.wait_time_ms - ISNULL(b.wait_time_ms, 0) AS delta_wait_ms,
w.signal_wait_time_ms - ISNULL(b.signal_wait_time_ms, 0) AS delta_signal_ms
FROM sys.dm_os_wait_stats AS w
LEFT JOIN #WaitStart AS b ON b.wait_type = w.wait_type
WHERE w.wait_type = N'PAGEIOLATCH_SH';
| Wait Type | Delta Task | Delta Wait | Delta Signal |
|---|
| PAGEIOLATCH_SH | 38 | 8,420 ms | 310 ms |
Delta را به نرخ بر ثانیه و تعداد تراکنش نرمال کنید. یک پنجره پنجثانیهای برای آموزش است و در سامانه کمترافیک ممکن است نماینده نباشد.
مثال 3: یافتن Requestهای فعال روی PAGEIOLATCH_SH
وقتی PAGEIOLATCH_SH در لحظه رخ میدهد، Session، وضعیت، منبع انتظار و Blocker را یکجا ثبت کنید. این Snapshot به اتصال آمار Instance-level به رخداد واقعی کمک میکند.
SELECT
r.session_id,
DB_NAME(r.database_id) AS database_name,
r.status,
r.command,
r.wait_time,
r.wait_resource,
r.blocking_session_id,
r.cpu_time,
r.total_elapsed_time
FROM sys.dm_exec_requests AS r
WHERE r.wait_type = N'PAGEIOLATCH_SH'
ORDER BY r.wait_time DESC;
| Session | پایگاه داده | منبع | زمان |
|---|
| 74 | a00b | PAGEIOLATCH_SH resource | 4,280 ms |
اگر خروجی خالی است، انتظار در همان لحظه فعال نیست؛ این موضوع با بالا بودن مقدار تجمعی تناقض ندارد و اهمیت Capture دورهای را نشان میدهد.
مثال 4: بررسی Taskها و Contextهای منتظر PAGEIOLATCH_SH
یک Request میتواند چند Task داشته باشد، بهویژه در Plan موازی. sys.dm_os_waiting_tasks جزئیات worker و resource_description را برای تحلیل سطح پایینتر فراهم میکند.
SELECT
wt.session_id,
wt.exec_context_id,
wt.wait_duration_ms,
wt.wait_type,
wt.blocking_session_id,
wt.resource_description
FROM sys.dm_os_waiting_tasks AS wt
WHERE wt.wait_type = N'PAGEIOLATCH_SH'
ORDER BY wt.wait_duration_ms DESC;
| Session | Context | مدت | Resource |
|---|
| 74 | 0 | 4,280 ms | resource details |
تعداد Taskها را با DOP، Blocking و ماهیت ورودی و خروجی صفحه داده تفسیر کنید؛ یک Task منفرد و کوتاه با صف گسترده و پایدار یکسان نیست.
مثال 5: نمایش متن SQL عامل PAGEIOLATCH_SH
برای رسیدن از Wait به اقدام اصلاحی باید متن Batch و Statement جاری را ثبت کرد. Offsetها باعث میشوند بخش در حال اجرای Batch جدا از متن کامل نمایش داده شود.
SELECT
r.session_id,
r.wait_time,
r.wait_resource,
SUBSTRING(st.text,
(r.statement_start_offset / 2) + 1,
((CASE r.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE r.statement_end_offset
END - r.statement_start_offset) / 2) + 1) AS current_statement,
st.text AS batch_text
FROM sys.dm_exec_requests AS r
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
WHERE r.wait_type = N'PAGEIOLATCH_SH'
ORDER BY r.wait_time DESC;
| Session | Wait | Statement | Batch |
|---|
| 74 | 4,280 ms | SELECT/UPDATE جاری | نام Procedure یا Batch |
متن Query ممکن است اطلاعات حساس داشته باشد؛ آن را در مخزن مانیتورینگ امن نگه دارید و برای اصلاح PAGEIOLATCH_SH حتماً Execution Plan و پارامترها را نیز جمعآوری کنید.
مثال 6: اندازهگیری Latency فایلهای داده مرتبط با PAGEIOLATCH_SH
برای تشخیص اینکه PAGEIOLATCH_SH از Storage میآید یا از حجم زیاد Physical Read، متوسط زمان خواندن و نوشتن هر فایل داده را جدا کنید. عدد فایل بهتنهایی کافی نیست و باید با Delta انتظار و Queryهای همان بازه مقایسه شود.
SELECT
DB_NAME(vfs.database_id) AS database_name,
mf.file_id,
mf.name AS logical_file_name,
CAST(vfs.io_stall_read_ms * 1.0 /
NULLIF(vfs.num_of_reads, 0) AS decimal(18,2)) AS avg_read_ms,
CAST(vfs.io_stall_write_ms * 1.0 /
NULLIF(vfs.num_of_writes, 0) AS decimal(18,2)) AS avg_write_ms
FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs
JOIN sys.master_files AS mf
ON mf.database_id = vfs.database_id
AND mf.file_id = vfs.file_id
WHERE mf.type_desc = N'ROWS'
ORDER BY avg_read_ms DESC;
| پایگاه داده | فایل | میانگین خواندن | میانگین نوشتن |
|---|
| a00b | a00b_Data | 18.40 ms | 4.10 ms |
اگر avg_read_ms فایل مسئلهدار همزمان با رشد PAGEIOLATCH_SH بالا باشد، مسیر Storage جدی است؛ اگر Latency مناسب اما تعداد خواندن بسیار زیاد باشد، Plan و ایندکس اولویت بیشتری دارند.
مثال 7: یافتن Queryهای دارای Physical Read بالا برای PAGEIOLATCH_SH
این گزارش Cache را بر اساس Physical Read مرتب میکند تا Queryهایی که احتمالاً صفحههای زیادی را از Storage وارد Buffer Pool کردهاند پیدا شوند. نتیجه را بهعنوان سرنخ بگیرید، زیرا آمار Cache با Recompile و Restart تغییر میکند.
SELECT TOP (10)
qs.execution_count,
qs.total_physical_reads,
qs.last_physical_reads,
qs.total_logical_reads,
DB_NAME(st.dbid) AS database_name,
LEFT(st.text, 300) AS sql_text
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
WHERE qs.total_physical_reads > 0
ORDER BY qs.total_physical_reads DESC;
| اجرا | Physical Read | Logical Read | متن Query |
|---|
| 42 | 182400 | 980 | 2401300 | گزارش فروش ماهانه |
Query پرتکرار با نسبت بالای Physical Read نامزد بررسی Execution Plan، پوشش ایندکس و الگوی Cache است؛ این Query الزاماً تنها عامل PAGEIOLATCH_SH نیست.
مثال 8: بررسی چیدمان و اندازه فایلهای داده هنگام PAGEIOLATCH_SH
فایلهای کوچک با رشد خودکار پرتکرار یا توزیع نامتوازن I/O میتوانند علائم را پیچیده کنند. این Query اندازه فعلی، Growth و مسیر فیزیکی را برای مرور معماری Storage برمیگرداند.
SELECT
DB_NAME(database_id) AS database_name,
file_id,
name AS logical_name,
physical_name,
CAST(size / 128.0 AS decimal(18,2)) AS size_mb,
CASE WHEN is_percent_growth = 1
THEN CONCAT(growth, N'%')
ELSE CONCAT(CAST(growth / 128.0 AS decimal(18,2)), N' MB')
END AS growth_setting
FROM sys.master_files
WHERE type_desc = N'ROWS'
ORDER BY database_id, file_id;
| پایگاه داده | File ID | اندازه | رشد |
|---|
| a00b | 1 | 81920 MB | 4096 MB |
رشد ثابت و ازپیشبرنامهریزیشده معمولاً از درصدی بهتر قابل پیشبینی است، اما اصلاح File Growth جای Tuning Query یا سنجش Storage را نمیگیرد.
مثال 9: مقایسه PAGEIOLATCH_SH با Waitهای همخانواده
Wait Typeها در خلأ تفسیر نمیشوند. مقایسه PAGEIOLATCH_SH با PAGEIOLATCH_EX، IO_COMPLETION، SOS_SCHEDULER_YIELD کمک میکند بفهمید علامت اصلی به کدام زیرسیستم نزدیکتر است.
SELECT
wait_type,
waiting_tasks_count,
wait_time_ms,
signal_wait_time_ms,
CAST(100.0 * wait_time_ms /
NULLIF(SUM(wait_time_ms) OVER (), 0) AS decimal(10,2)) AS family_percent
FROM sys.dm_os_wait_stats
WHERE wait_type IN (N'PAGEIOLATCH_SH', N'PAGEIOLATCH_EX', N'IO_COMPLETION', N'SOS_SCHEDULER_YIELD')
ORDER BY wait_time_ms DESC;
| Wait Type | تعداد | زمان | سهم خانواده |
|---|
| PAGEIOLATCH_SH | 12,480 | 3,842,000 ms | 64.20% |
| PAGEIOLATCH_EX | 4,110 | 1,820,000 ms | 30.42% |
سهم خانواده برای Prioritization مفید است، ولی شدت کسبوکاری را نشان نمیدهد. زمان پاسخ، SLA و تعداد درخواست متاثر را هم اضافه کنید.
مثال 10: ساخت قاعده هشدار مستقل برای Delta PAGEIOLATCH_SH
در مانیتورینگ بهتر است روی Delta یک بازه ثابت و نرخ هر ثانیه هشدار بسازید، نه روی مقدار تجمعی از Startup. مثال مستقل زیر با داده نمونه منطق سطحبندی را نمایش میدهد و در سامانه مانیتورینگ با Snapshot واقعی جایگزین میشود.
DECLARE @WindowSeconds int = 60;
DECLARE @WaitDelta TABLE
(
wait_type nvarchar(60),
delta_wait_ms bigint,
delta_tasks bigint
);
INSERT INTO @WaitDelta (wait_type, delta_wait_ms, delta_tasks)
VALUES (N'PAGEIOLATCH_SH', 8400, 38);
SELECT
wait_type,
delta_wait_ms,
delta_tasks,
CAST(delta_wait_ms * 1.0 / @WindowSeconds AS decimal(18,2)) AS wait_ms_per_second,
CASE
WHEN delta_wait_ms >= 30000 THEN N'بحرانی'
WHEN delta_wait_ms >= 5000 THEN N'نیازمند بررسی'
ELSE N'عادی'
END AS alert_level
FROM @WaitDelta;
| Wait Type | Delta | نرخ | سطح |
|---|
| PAGEIOLATCH_SH | 8,400 ms | 140 ms/s | نیازمند بررسی |
آستانههای نمونه عمومی نیستند. آنها را از Baseline، ظرفیت و SLA سامانه خود استخراج کنید تا برای PAGEIOLATCH_SH هشدار کاذب تولید نشود.
سؤالات متداول
سؤال 1: PAGEIOLATCH_SH دقیقاً چه زمانی در SQL Server ثبت میشود؟
این انتظار زمانی ثبت میشود که یک worker برای خواندن صفحهای از فایل داده به Buffer Pool منتظر تکمیل I/O و گرفتن لچ اشتراکی است. طولانی شدن آن معمولاً به تأخیر خواندن Storage، Physical Read زیاد یا فشار حافظه اشاره میکند. ثبت یک انتظار کوتاه بخشی طبیعی از زمانبندی موتور است؛ اهمیت زمانی ایجاد میشود که Delta آن با کندی قابل مشاهده و افت SLA همزمان باشد.
سؤال 2: برای شروع تحلیل PAGEIOLATCH_SH کدام DMVها مناسباند؟
sys.dm_os_wait_stats برای روند Instance، sys.dm_exec_requests برای Request جاری و sys.dm_os_waiting_tasks برای Task و منبع انتظار نقطه شروع هستند. با توجه به دسته ورودی و خروجی صفحه داده باید DMV تخصصی همان زیرسیستم و Execution Plan نیز افزوده شود.
سؤال 3: آیا زیاد بودن PAGEIOLATCH_SH به معنی نیاز فوری به سختافزار جدید است؟
خیر. خرید Storage سریعتر بدون یافتن Query پرخوانش ممکن است فقط علامت را پنهان کند و هزینه را بالا ببرد. پیش از خرید باید هزینه اصلاح Query، تنظیمات، معماری برنامه و زیرساخت با اندازهگیری قبل و بعد مقایسه شود؛ یک ارزیابی فنی حرفهای میتواند از هزینه اشتباه جلوگیری کند.
سؤال 4: چه زمانی تحلیل حرفهای PAGEIOLATCH_SH برای یک کسبوکار توجیه دارد؟
وقتی انتظار با افت فروش، Timeout، ناتمام ماندن Job، افزایش زمان پاسخ یا نقض SLA همبسته است، تحلیل ساختاریافته ارزش تجاری دارد. مشاوره SQL Server در این مرحله باید خروجی قابل سنجش مانند کاهش p95 و نرخ انتظار ارائه کند، نه فقط تغییر چند تنظیم.
سؤال 5: تفاوت PAGEIOLATCH_SH با PAGEIOLATCH_EX چیست؟
PAGEIOLATCH_SH در دسته ورودی و خروجی صفحه داده تفسیر میشود، در حالی که PAGEIOLATCH_EX مرحله یا منبع متفاوتی را برجسته میکند. همزمانی این دو ممکن است زنجیره علت و معلول باشد؛ تعریف هر Wait، منبع جاری و Timeline تعیین میکند کدامیک علامت و کدامیک علت نزدیکتر است.
سؤال 6: برای سفارش پروژه کاهش PAGEIOLATCH_SH چه دادههایی باید آماده شود؟
بازه دقیق کندی، نسخه و Edition، Wait Delta، Query و Plan، شاخصهای سیستمعامل، توپولوژی و SLA را آماده کنید. حذف اطلاعات حساس و فراهم کردن نمونه قابل بازتولید باعث میشود خدمت عیبیابی سریعتر، کمریسکتر و قابل ارزیابی باشد.
سؤال 7: رایجترین خطای تشخیصی درباره PAGEIOLATCH_SH چیست؟
قضاوت با مقدار تجمعی از زمان Startup و تغییر تنظیمات بدون اتصال انتظار به Query و بازه حادثه رایجترین خطاست. خرید Storage سریعتر بدون یافتن Query پرخوانش ممکن است فقط علامت را پنهان کند و هزینه را بالا ببرد. Snapshot کوتاه، Baseline و مقایسه قبل و بعد این خطا را کاهش میدهد.
سؤال 8: اثر PAGEIOLATCH_SH بر Performance چگونه اندازهگیری میشود؟
Delta wait_time_ms، تعداد Task، متوسط انتظار و نرخ بر ثانیه را کنار throughput، p95 زمان پاسخ و منابع سیستم ثبت کنید. افزایش زمان پاسخ کوئریهای خواندنی، کاهش Throughput و انباشته شدن درخواستهای وابسته به صفحههای دیسکی بنابراین معیار فنی باید به معیار تجربه کاربر یا Job کسبوکار متصل شود.
سؤال 9: Best Practice اصلی برای مدیریت PAGEIOLATCH_SH چیست؟
ابتدا Delta انتظار و Latency هر فایل را در یک بازه پرترافیک اندازه بگیرید؛ سپس Plan و Logical/Physical Read کوئریهای همان بازه را بررسی کنید. تغییر را ابتدا در محیط آزمایش یا روی دامنه محدود اعمال کنید، معیار موفقیت را از قبل بنویسید و امکان بازگشت داشته باشید. پاک کردن Wait Stats بدون ذخیره Baseline یا اجرای چند تغییر همزمان قابلیت استناد نتیجه را از بین میبرد.
سؤال 10: PAGEIOLATCH_SH با کدام نسخههای SQL Server سازگار است؟
نسخههای متداول SQL Server و Azure SQL. ستونها و Permissionهای DMV ممکن است میان نسخههای On-premises، Azure SQL و نسخههای جدید تغییر کنند؛ Query تشخیصی را در مستندات همان نسخه کنترل و ابتدا با دسترسی Read-only مناسب اجرا کنید.