תוכן ענינים
SQL Server איטי הוא סימפטום, לא אבחנה. כשמשתמשים מתלוננים שהמערכת “נתקעת”, הדחף הטבעי הוא לפעול מיד: להוסיף זיכרון, להפעיל מחדש את השרת או לבנות עוד Index. לפעמים זה עוזר לרגע,
אבל בדרך כלל הבעיה חוזרת, כי איש לא בדק מה באמת גרם לה.
הכתבה הזו מציגה את הגישה ההפוכה: קודם אבחון, אחר כך תיקון. נעבור על הסיבות הנפוצות לאיטיות, על הכלים ש-SQL Server עצמו מספק כדי לאתר אותן, ועל הנקודה שבה כדאי להביא מומחה.
1. למה SQL Server נהיה איטי
כמעט אף פעם לא בגלל סיבה אחת. מערכת שעבדה טוב לפני שנה פוגשת היום יותר נתונים, יותר משתמשים, קוד חדש ושינויי תצורה שאיש לא תיעד. הסיבות הנפוצות הן:
- שאילתות שנכתבו לנפח נתונים קטן ולא מחזיקות מעמד כשהטבלאות גדלות.
- Indexes חסרים, כפולים או לא מתאימים לדפוסי השימוש בפועל.
- נעילות (Blocking) בין טרנזקציות שמחכות אחת לשנייה.
- Statistics לא מעודכנות שגורמות ל-Optimizer לבחור תוכנית ביצוע גרועה.
- לחץ על משאבים: מעבד, זיכרון או דיסק.
- תחזוקה שלא בוצעה: גידול קבצים לא מתוכנן, Log שמתנפח, Jobs שנכשלו בשקט.
הבעיה היא שכל הסיבות האלה נראות למשתמש אותו דבר: “המערכת איטית”. לכן התחלה נכונה היא תמיד שאלה, לא פעולה.
2. איך יודעים שהבעיה בכלל ב-SQL Server
לא כל איטיות של אפליקציה מתחילה במסד הנתונים. לפני שנוגעים בשרת, כדאי לבודד את השכבה:
האם השאילתה עצמה איטית? אפשר להריץ אותה ישירות דרך SQL Server Management Studio. אם השאילתה חוזרת בתוך שנייה אבל המשתמש ממתין עשר שניות למסך, ייתכן שהבעיה נמצאת בשרת האפליקציה,
ברשת או בקוד שמעבד את התוצאות.
שימו לב: שאילתה מהירה ב-SSMS לא מוכיחה שהבעיה מחוץ ל-SQL Server. SSMS מתחבר עם הגדרות שונות מאלה של רוב האפליקציות (למשל ARITHABORT), ולכן SQL Server עשוי ליצור עבורו תוכנית ביצוע נפרדת. בנוסף, הרצה עם ערכים קבועים או משתנים מקומיים במקום פרמטרים, ונתונים שכבר נמצאים בזיכרון, עלולים להסתיר בעיות כמו Parameter Sniffing. כדי להשוות נכון, כדאי לבדוק את תוכנית הביצוע שהאפליקציה עצמה מקבלת, למשל דרך Query Store.
האם האיטיות כללית או נקודתית? מסך אחד איטי מצביע על שאילתה מסוימת. כל המערכת איטית מצביע על משאבים, Blocking או תחזוקה.
מתי זה התחיל? עדכון גרסה, Deploy חדש, טעינת נתונים גדולה או שינוי תשתית הם חשודים מיידיים.
מה SQL Server עושה ברגע זה? SQL Server חושף מידע על הפעילות הנוכחית דרך Dynamic Management Views. בעזרתם אפשר לראות אילו בקשות רצות, כמה זמן הן נמשכות, ואם הן ממתינות למשאב כלשהו, כמו נעילה, דיסק או זיכרון. זו נקודת פתיחה טובה כדי להבין אם השרת עסוק, חסום או פשוט ממתין לאפליקציה.
חשוב לזכור שגם ההפך יכול לקרות. דפוסי עבודה של האפליקציה, כמו ריבוי שאילתות קטנות, טיפול לא יעיל בחיבורים או משיכת נתונים רבים מהנדרש, יכולים לגרום למסד הנתונים להיראות כמקור הבעיה, בעוד שהפתרון נמצא בצד האפליקציה.
3. שאילתות איטיות
ברוב המקרים, מעט שאילתות אחראיות לרוב העומס. המטרה היא למצוא אותן לפי צריכת משאבים מצטברת, ולא לפי מה שמשתמש אחד התלונן עליו. שאילתה שרצה 200 מילישניות אבל מופעלת 50,000 פעם בשעה מכבידה יותר משאילתת דוח שרצה דקה פעם ביום.
הכלי המרכזי לכך הוא Query Store. הוא שומר היסטוריה של שאילתות, תוכניות ביצוע ונתוני ריצה, כך שאפשר לראות מתי שאילתה החלה להאט ואיזו תוכנית השתנתה. החל מ-SQL Server 2022 הוא מופעל כברירת מחדל במסדי נתונים חדשים, אבל מסדים ששוחזרו או שודרגו שומרים על ההגדרה הקודמת. בגרסאות 2016–2019 צריך להפעיל אותו ידנית, ושווה לבדוק שהוא אכן פעיל.
כשאין Query Store, אפשר להיעזר ב-sys.dm_exec_query_stats, שמחזיק נתונים מצטברים על שאילתות שנמצאות כרגע ב-Plan Cache. החיסרון: הנתונים נמחקים באתחול השרת.
4. Indexes לא נכונים או חסרים
Index חסר הוא הסיבה הקלאסית לשאילתה שסורקת טבלה של מיליוני שורות כדי להחזיר עשר. אבל הפתרון ההפוך, להוסיף Index לכל עמודה, יוצר בעיה אחרת: כל INSERT, UPDATE ו-DELETE צריך לעדכן את כל ה-Indexes, והכתיבה נעשית איטית.
SQL Server מציע המלצות דרך ה-DMVs של Missing Indexes ובחלון ה-Execution Plan. כדאי להתייחס אליהן כנקודת פתיחה בלבד: הן לא מתחשבות בעלות התחזוקה, לא מזהות Indexes קיימים שכמעט זהים, ולעיתים מציעות כמה וריאציות של אותו Index.
בדיקה נכונה כוללת גם את הצד השני: אילו Indexes קיימים ולא נקראים כמעט אף פעם (sys.dm_db_index_usage_stats), אילו כפולים, ואילו רחבים מדי. לעיתים קרובות, מחיקה של Indexes מיותרים משפרת ביצועים לא פחות מהוספה.
לפני שמוחקים Index: נתוני השימוש ב-Indexes מתאפסים בכל אתחול של השרת, ולכן הם משקפים רק את התקופה שעברה מאז. אם השרת אותחל לפני שבוע, Index שמשמש דוח חודשי ייראה כלא בשימוש, ומחיקתו עלולה לפגוע בדוח בפעם הבאה שירוץ. גם Index שנראה כפול לא תמיד מיותר: הוא יכול לאכוף ייחודיות (Unique), או להיות קטן ויעיל יותר עבור חלק מהשאילתות. לכן מחיקת Index היא החלטה שדורשת שיקול דעת, ולא רק קריאה של נתוני השימוש.
5. Execution Plan: להבין מה SQL Server באמת עושה
Execution Plan מציג כיצד SQL Server החליט לבצע Query:
- באיזה Index להשתמש.
- באיזה סוג Join.
- באיזה סדר לקרוא טבלאות.
- כמה שורות הוא מעריך שיעברו בכל שלב.
זהו אחד הכלים החשובים ביותר להבנת הסיבה לכך ש-Query איטית.
אין רשימה אחת שמכסה את כל מה שאפשר למצוא ב-Plan, אבל אלה כמה מהסימנים הנפוצים שכדאי לשים לב אליהם:
- פער גדול בין Estimated Rows ל-Actual Rows.
- חצים עבים בין אופרטורים, שמעידים על כמות גדולה של שורות שעוברת בין השלבים.
- אופרטורים עם Cost יחסי גבוה. ה-Cost הוא הערכה של ה-Optimizer ולא מדידה בפועל, ולכן הוא מכוון לאן להסתכל אבל לא מוכיח דבר.
- Parallelism לא צפוי, למשל בשאילתה שאמורה להיות קלה, או חלוקת עבודה לא מאוזנת בין Threads.
- Table Scan או Index Scan על טבלה גדולה כשמוחזרות מעט שורות.
- Key Lookups שמתבצעים מספר רב מאוד של פעמים.
- Spill ל-tempdb.
- Implicit Conversion שפוגע באפשרות להשתמש ב-Index.
פער משמעותי בין מספר השורות המשוער למספר בפועל מעיד שה-Optimizer קיבל החלטה על בסיס הערכה שגויה. הסיבה יכולה להיות Statistics שאינן משקפות את הנתונים, אבל גם Parameter Sniffing, פונקציות או חישובים על עמודות בתנאי WHERE, או תנאים מורכבים שקשה להעריך. לכן הפער מצביע על המקום שצריך לבדוק, ולא על הסיבה עצמה.
כש-Query הייתה מהירה אתמול ואיטית היום, ללא שינוי בקוד, כמעט תמיד ה-Plan השתנה. השאלה החשובה היא מה גרם לשינוי: Statistics שהתעדכנו, ערך פרמטר שונה בזמן הקומפילציה, Plan Cache שנוקה, Index שנוסף או הוסר, שדרוג גרסה או Compatibility Level, או שינוי בהתפלגות הנתונים.
Query Store שימושי במיוחד במקרים כאלה, משום שהוא מאפשר להשוות בין תוכניות היסטוריות ואף לבצע Plan Forcing כפתרון זמני, בזמן שמטפלים בשורש הבעיה.
6. Blocking ו-Deadlocks
שני המושגים נשמעים דומים, אבל הם שונים מהותית.
Blocking קורה כשטרנזקציה אחת מחזיקה נעילה ואחרות ממתינות לה. זה חלק תקין מעבודת מסד נתונים, וזה הופך לבעיה רק כשההמתנה מתארכת. הדפוס המוכר: טרנזקציה פתוחה שאיש לא סגר, Job לילי שגלש לשעות העבודה, או עדכון גדול בלי Index מתאים שנועל יותר משצריך. מכיוון שהשאילתה החוסמת עצמה יכולה להיות “שקטה”, מסתכלים על שרשרת ה-blocking_session_id ב-sys.dm_exec_requests ומחפשים את ראש השרשרת.
Deadlock נוצר כאשר שני תהליכים או יותר מחכים זה למשאב שמוחזק בידי האחר, ולכן אף אחד מהם לא יכול להתקדם.
SQL Server מזהה את המצב (תהליך Lock Monitor סורק כברירת מחדל כל 5 שניות) ובוחר אחת מה-Transactions כקורבן. אותה Transaction מבוטלת ומקבלת Error 1205. כברירת מחדל השגיאה לא נכתבת ל-Error Log, כך שאפשר לחיות זמן רב עם Deadlocks בלי לדעת.
פרטי Deadlocks נשמרים גם ב-Extended Events session בשם system_health, שפועל כברירת מחדל וכולל Deadlock Graph שמאפשר לנתח את האירוע.
אין פתרון אחד ל-Deadlocks, והגורם משתנה ממקרה למקרה. לעיתים קרובות הבעיה מתחילה בביצועים: שאילתה איטית או Index חסר גורמים לכך שנעילות נלקחות על יותר שורות ומוחזקות זמן רב יותר, וכך גדל הסיכוי להתנגשות. גורמים נוספים הם Transactions ארוכות, סדר גישה לא עקבי לטבלאות, ו-Lock Escalation, שבו SQL Server מחליף נעילות רבות ברמת שורה או דף בנעילה רחבה יותר על הטבלה כולה. לכן כדאי לבדוק קודם את השאילתות, ה-Indexes ומבנה ה-Transactions, ורק לאחר מכן לשקול שינוי Isolation Level, שמשנה את התנהגות המערכת ודורש בדיקה זהירה.
7. Wait Statistics
אם יש כלי אחד שמגלם את גישת Diagnosis first, זה Wait Statistics. כל פעם ש-thread ב-SQL Server לא יכול להתקדם, הוא נרשם כממתין לסוג משאב מסוים. sys.dm_os_wait_stats מסכם את כל ההמתנות האלה ומראה איפה השרת מבזבז את רוב הזמן.
| Wait Type | מה הוא יכול להעיד |
|---|---|
| PAGEIOLATCH_* | המתנה לקריאת דפים מהדיסק. לא תמיד מעיד על דיסק איטי: לעיתים קרובות הסיבה היא שאילתה שסורקת כמות גדולה של נתונים, או זיכרון שאינו מספיק כדי להחזיק את הנתונים הפעילים |
| LCK_M_* | המתנה ל-Locks ו-Blocking |
| WRITELOG | המתנה לכתיבה ל-Transaction Log |
| CXPACKET / CXCONSUMER | עבודה מקבילית; לא בהכרח בעיה בפני עצמה |
| SOS_SCHEDULER_YIELD | לחץ אפשרי על CPU |
| RESOURCE_SEMAPHORE | Queries שממתינות להקצאת Memory. הסיבה יכולה להיות מחסור בזיכרון, אבל גם Statistics לא מעודכנות שגורמות להערכה שגויה ולבקשת זיכרון גדולה מהנדרש |
| ASYNC_NETWORK_IO | האפליקציה אינה צורכת את התוצאות בקצב שבו SQL Server מספק אותן |
שתי הסתייגויות חשובות. ראשית, המונים מצטברים מאז האתחול האחרון או מאז איפוס ידני, ולכן המספרים המוחלטים פחות חשובים מהשינוי לאורך זמן. שנית, חלק מסוגי ההמתנה הם “רעש” של תהליכי רקע תקינים ויש לסנן אותם. סוג ההמתנה הוא כיוון, לא אבחנה סופית: PAGEIOLATCH גבוה יכול לנבוע מדיסק חלש, אבל גם מ-Index חסר שגורם לסריקות מיותרות.
8. CPU, Memory ו-I/O
שלושת המשאבים קשורים זה בזה, ולכן קל לפרש אותם לא נכון.
CPU גבוה הוא לרוב תוצאה ולא סיבה: שאילתות שסורקות טבלאות שלמות, פונקציות שמופעלות על כל שורה, או קומפילציות חוזרות של שאילתות שלא עוברות פרמטריזציה. לפני שמוסיפים ליבות, בודקים אילו שאילתות צורכות את ה-CPU.
Memory – SQL Server מתוכנן לתפוס זיכרון ולהחזיק בו כ-Buffer Pool, כך שצריכת זיכרון גבוהה במנהל המשימות היא מצב תקין. מה שכן חשוב: שההגדרה max server memory משאירה מספיק זיכרון למערכת ההפעלה, ושהשרת לא נאלץ לפנות דפים מהזיכרון שוב ושוב (מדד Page Life Expectancy שקורס בפתאומיות הוא סימן).
I/O – המדידה המדויקת ביותר של זמן ההשהיה (Latency) היא ברמת מערכת ההפעלה, באמצעות Performance Monitor (PerfMon), עם מונים כמו Avg. Disk sec/Read ו-Avg. Disk sec/Write. מכיוון שהמדידה נעשית בזמן אמת, היא מאפשרת לזהות גם קפיצות קצרות. כהשלמה, sys.dm_io_virtual_file_stats מראה את ה-Latency מנקודת המבט של SQL Server לכל קובץ בנפרד, אבל הנתונים בו מצטברים מאז האתחול האחרון, וממוצע ארוך עלול להסתיר בעיות נקודתיות. מעניינים במיוחד קבצי ה-Data, ה-Transaction Log ו-tempdb.
גם כאן, חשוב לא למהר להאשים את ה-Storage. כשהזיכרון קטן מדי או ה-Indexes לא מתאימים, השרת קורא מהדיסק הרבה יותר מהנדרש, והדיסק “נראה” איטי.
9. Statistics לא מעודכנות
ה-Optimizer לא קורא את הטבלה לפני שהוא בונה תוכנית; הוא מסתמך על Statistics, היסטוגרמה שמתארת את התפלגות הערכים. כשהן לא משקפות את המציאות, הוא עלול להעריך 10 שורות כשבפועל יש מיליון, ולבחור תוכנית שמתאימה למקרה הראשון.
Auto Update Statistics פעיל כברירת מחדל, אבל העדכון מתבצע רק אחרי שמספר השינויים חוצה סף. בגרסאות ישנות או ב-Compatibility Level נמוך מ-130 הסף עמד על כ-20% מהשורות. החל מ-SQL Server 2016 וב-Compatibility Level 130 ומעלה, הסף הוא דינמי: שורש ריבועי של 1,000 כפול מספר השורות. בטבלה של מיליון שורות, למשל, העדכון יקרה אחרי כ-31,600 שינויים במקום 200,000.
זה שיפור, אבל לא מספיק לכל מצב. טבלאות שבהן נתונים חדשים נכנסים תמיד בסוף הטווח (למשל תאריכים) נוטות להראות ל-Optimizer “עבר” ולא “הווה”. לכן מסדים פעילים צריכים גם Job תחזוקה שמעדכן Statistics באופן יזום, לצד תחזוקת Indexes.
10. Database growth ו-Capacity
מסד נתונים שגדל פי עשרה בשלוש שנים הוא כבר לא אותה מערכת, גם אם הקוד לא השתנה. שאילתות שסרקו בעבר 100 אלף שורות סורקות היום מיליון, חלונות התחזוקה מתארכים, וגיבויים ושחזורים לוקחים יותר זמן.
יש גם השפעה ישירה של אופן הגידול. כשקובץ צריך לגדול באמצע עבודה, כל טרנזקציה שתלויה בגידול ממתינה לו. קובץ ה-Log רגיש במיוחד: עד SQL Server 2022 הוא לא יכול היה ליהנות מ-Instant File Initialization כלל, ומ-2022 רק בגידולים של עד 64MB. גידולים קטנים ותכופים מייצרים גם מספר גבוה של Virtual Log Files, שמאט שחזורים והפעלה מחדש.
ההמלצה המעשית: להקצות מקום מראש לפי תחזית, להגדיר Autogrowth כגודל קבוע ולא באחוזים, ולהתייחס לגידול אוטומטי כרשת ביטחון ולא כמנגנון העבודה הרגיל. מעקב אחרי קצב הגידול הוא גם הבסיס לתכנון נכון של גיבוי SQL Server ושל חלונות התחזוקה.
11. למה הוספת חומרה לא תמיד פותרת את הבעיה
הוספת זיכרון או ליבות היא הפתרון המפתה ביותר, כי הוא לא דורש להבין את הבעיה. לפעמים הוא באמת נכון, למשל כשהשרת פשוט קטן מדי לנפח העבודה. אבל ברוב המקרים הוא רק דוחה את הבעיה:
- שאילתה שסורקת טבלה שלמה בגלל Index חסר תסרוק אותה גם בשרת כפול. היא תהיה מהירה יותר לזמן מה, עד שהטבלה תגדל שוב.
- Blocking ו-Deadlocks לא נפתרים בחומרה, כי הם תלויים בלוגיקה של הטרנזקציות.
- תוכנית ביצוע שגויה בגלל Statistics תישאר שגויה על כל חומרה.
- הוספת ליבות עלולה להגדיל עלויות רישוי, כי SQL Server מתומחר ברוב המקרים לפי ליבה.
השאלה הנכונה היא לא “כמה חומרה צריך”, אלא “על מה השרת מבזבז את המשאבים שכבר יש לו”. רק אחרי שעונים עליה אפשר לדעת אם חומרה נוספת היא השקעה או בזבוז.
12. איך נכון לבצע Performance Tuning
Performance Tuning טוב הוא תהליך מסודר, לא אוסף טריקים. הסדר חשוב:
- מגדירים את הבעיה. איזה תהליך איטי, ממתי, וכמה הוא איטי במספרים.
- אוספים נתונים לפני שנוגעים. Wait Statistics, השאילתות הכבדות מ-Query Store, מצב Blocking ומדדי משאבים. זה גם קו הבסיס להשוואה.
- מזהים את הצוואר הצר העיקרי. לרוב יש אחד או שניים שאחראים לרוב הבעיה.
- משנים דבר אחד בכל פעם. Index, שכתוב שאילתה, עדכון Statistics או שינוי הגדרה. כמה שינויים במקביל לא מאפשרים לדעת מה עזר ומה הזיק.
- מודדים שוב. משווים לקו הבסיס, ובודקים שהשיפור לא פגע במקום אחר, למשל בכתיבה.
- מתעדים. מה שונה, למה ומה התוצאה, כדי שהתיקון לא ייעלם בעדכון הבא.
כדאי לבצע שינויים קודם בסביבת בדיקות עם נתונים מייצגים, ולוודא שיש גיבוי עדכני לפני כל שינוי בסביבת הייצור.
13. ניטור רציף מול טיפול רק כשהמערכת איטית
הבעיה בטיפול רק כשמשתמשים מתלוננים היא שבאותו רגע חסר המידע החשוב ביותר: איך המערכת נראתה כשהיא עבדה טוב. בלי קו בסיס, קשה לדעת אם 40% CPU הם חריגה או שגרה, ואם ה-Wait העיקרי השתנה.
| טיפול תגובתי | ניטור רציף | |
|---|---|---|
| מתי מגלים את הבעיה | כשהמשתמשים כבר מרגישים אותה | לעיתים קרובות לפני שהיא מורגשת |
| נתונים לאבחון | מה שקורה ברגע האירוע בלבד | היסטוריה ומגמות לאורך זמן |
| Deadlocks ו-Jobs שנכשלו | עלולים לעבור בשקט | מזוהים ומדווחים |
| תכנון Capacity | ניחוש | לפי קצב גידול בפועל |
| עלות | שעות חירום ופגיעה עסקית | עבודה שוטפת ומתוכננת |
ניטור טוב אינו רק התראה על שרת שנפל. הוא כולל מעקב אחרי Wait Statistics, שאילתות כבדות, Blocking, זמני Jobs, גידול קבצים והצלחת גיבויים. זה בדיוק התפקיד של מערכת ניטור שרתים שמותאמת גם למסדי נתונים.
14. מתי צריך DBA
לא כל ארגון צריך DBA במשרה מלאה, אבל כמעט כל ארגון שמערכות הליבה שלו רצות על SQL Server צריך גישה לידע של DBA. סימנים שהגיע הזמן:
- אותן בעיות ביצועים חוזרות שוב ושוב, ואיש לא יודע להסביר למה.
- הפתרון הקבוע לכל תקלה הוא Restart או “עוד זיכרון”.
- אין קו בסיס, אין ניטור, ואין מי שבודק שהגיבויים אכן ניתנים לשחזור.
- מתוכנן שדרוג גרסה, מעבר לענן או גידול משמעותי בנפח העבודה.
- צוות הפיתוח או ה-IT עוסק במסד הנתונים “בין לבין”, בלי זמן ומומחיות ייעודיים.
לארגונים רבים, שירותי DBA בישראל במודל מנוהל או חלקי הם הדרך הנכונה: בדיקת בריאות ראשונית, Tuning ממוקד, ולאחר מכן תחזוקה וניטור שוטפים.
לתמונה הרחבה של התחום כדאי לקרוא גם על ניהול מסדי נתונים בארגון ועל ניהול MSSQL Server.
שאלות נפוצות
האם Restart לשרת פותר בעיות ביצועים?
לפעמים זמנית, כי הוא מנקה את ה-Plan Cache ומשחרר נעילות. אבל הוא גם מאפס נתוני אבחון חשובים כמו Wait Statistics ונתוני השימוש ב-Indexes, והבעיה בדרך כלל חוזרת.
האם SQL Server שצורך כמעט את כל הזיכרון הוא בעיה?
לא בהכרח. זו התנהגות מתוכננת: SQL Server מחזיק נתונים בזיכרון כדי לחסוך קריאות מהדיסק. חשוב לוודא ש-max server memory מוגדר כך שנשאר מספיק זיכרון למערכת ההפעלה.
האם כדאי ליישם את כל המלצות ה-Missing Indexes?
לא. הן נקודת פתיחה, אבל הן לא מתחשבות בעלות הכתיבה וב-Indexes קיימים. כל Index חדש צריך להיבחן בהקשר של השאילתות והעומס בפועל.
האם אפשר למחוק Indexes שנראים כלא בשימוש?
רק אחרי בדיקה זהירה. נתוני השימוש מתאפסים בכל אתחול, ולכן Index שמשמש דוח חודשי עלול להיראות מיותר. גם Index שנראה כפול יכול לאכוף ייחודיות או להיות יעיל יותר לחלק מהשאילתות.
מה ההבדל בין Blocking ל-Deadlock?
ב-Blocking טרנזקציה ממתינה עד שהשנייה תסיים. ב-Deadlock שתיים ממתינות זו לזו, ו-SQL Server מבטל אחת מהן.
השאילתה מהירה ב-SSMS ואיטית באפליקציה. למה?
SSMS מתחבר עם הגדרות שונות מרוב האפליקציות, ולכן עשוי לקבל תוכנית ביצוע אחרת. כדי להשוות נכון, בודקים את התוכנית שהאפליקציה עצמה מקבלת, למשל דרך Query Store.