EXPLAIN

הפקודה EXPLAIN לא מריצה את השאילתה בפועל. היא פשוט מבקשת מה-Optimizer של בסיס הנתונים: "תגיד לי איך היית מבצע את זה אם הייתי מבקשת".

מה זה Optimizer?

נחשוב עליו כמו על WAZE (או Google Maps, כל אחד והבחירה שלו בניווט 🙂). כשנכתוב ל DB שאילתה כמו: "תביא לי את כל הלקוחות שקנו בחודש האחרון", הוא יחזיר לנו את רשימת הלקוחות, אבל אנחנו לא יודעים איך הוא הגיע אליהם מאחורי הקלעים.

כאן נכנס ה Optimizer לתמונה. הוא מקבל את הבקשה שלך, ומבצע תהליך שנקרא Query Optimization המורכב מ 4 חלקים עיקריים:

  1. ניתוח: הוא בודק את השאילתה שלך ומפרק אותה לחלקים לוגיים.
  2. חיפוש נתיבים: הוא בוחן את כל הדרכים האפשריות לשלוף את המידע. למשל: האם לסרוק את כל הטבלה? (זה כמו לנסוע דרך כבישים צדדיים בלי תכנון), האם להשתמש באינדקס מסוים כדי לקפוץ ישר לשורות הרלוונטיות? (כמו לעלות על כביש מהיר), ובאיזה סדר לבצע את ה-Joins בין הטבלאות.
  3. חישוב עלות (Cost): ה-Optimizer מעריך את "העלות" של כל נתיב, כלומר, כמה משאבים (CPU, זיכרון, I/O מהדיסק) כל דרך תצרוך.
  4. בחירה: הוא בוחר את המסלול שנראה לו הכי מהיר ויעיל, וזוהי "תוכנית הביצוע" (Execution Plan) שהוא מוציא לפועל.
האם ה-Optimizer טועה? כן! ממש כמו WAZE, גם הוא מסתמך על סטטיסטיקות של ה-DB שלנו. אם הסטטיסטיקות לא מעודכנות (לדוגמה: טבלה שגדלה בפתאומיות וה-DB עדיין בטוח שהיא קטנה, או אינדקסים שנמחקו מהטבלה), הסטטיסטיקה תשתבש והוא עלול לבחור במסלול איטי.

ניתן להשוות את ה-EXPLAIN ל-Debugger ב-IDE. כמו שדיבאגר נותן לך לראות מה הState של המשתנים ואיזה שורות קוד רצות, EXPLAIN הוא הדיבאגר של הדאטה שלך.

דוגמת הרצה: איך מגיעים לתוצאה?

נניח ואנחנו מריצים את השאילתה הבאה עם הקידומת EXPLAIN:

EXPLAIN SELECT o.order_id, p.product_name, o.order_date FROM orders o
JOIN products p ON o.product_id = p.id
WHERE o.customer_id = 12345 AND o.order_date > '2026-01-01';

זו התוצאה (החלקית והחשובה) שיכולה להתקבל בבסיס הנתונים:

+-------------+-------+--------+---------------+-------------+---------+------+-----------------------+
| select_type | table | type   | possible_keys | key         | key_len | rows | Extra                 |
+-------------+-------+--------+---------------+-------------+---------+------+-----------------------+
| SIMPLE      | o     | ref    | customer_id   | customer_id | 4       |   15 | Using index condition |
| SIMPLE      | p     | eq_ref | PRIMARY       | PRIMARY     | 4       |    1 |                       |
+-------------+-------+--------+---------------+-------------+---------+------+-----------------------+

איך לקרוא את התוצאה?

🔑 העמודה type היא המפתח

בדוגמה שלנו מופיע ref ו-eq_ref. זה מצוין! זה אומר שה-Optimizer מצא אינדקס מתאים והשתמש בו. אם היינו רואים את אחד מהדגלים האדומים המפורטים בהמשך, זה הזמן לדאוג.

📊 העמודה rows

ה-DB מעריך שהוא ימצא 15 שורות בטבלת ה-orders ויבצע להן Join מול טבלת המוצרים. 15 שורות זה מספר מעולה. אם היית רואה כאן 1,000,000 – יש לנו בעיה רצינית.

🚀 העמודה Extra

הערך Using index condition אומר שהוא משתמש באינדקס כדי לסנן את השורות עוד לפני שהוא בכלל שולף אותן פיזית מהדיסק. זה יעיל ומהיר מאוד!

העמודה select_type והמשמעויות שלה
  • SIMPLE: שאילתה פשוטה ללא UNION, ללא Subquery וללא Derived tables (טבלאות זמניות).
  • PRIMARY: זהו החלק ה"חיצוני" ביותר של השאילתה. אם יש לך שאילתה מורכבת עם סאב-קווארי, ה-PRIMARY הוא זה שמנהל את הכל.
  • SUBQUERY: מופיעה תת-שאילתה (Subquery) בתוך ה-SELECT או ה-WHERE.
  • DERIVED: כשהשתמשת ב-Subquery בתוך ה-FROM (למשל: SELECT * FROM (SELECT ... ) AS temp_table). המשמעות היא שה-DB יצר טבלה זמנית בזיכרון (או בדיסק) כדי להריץ עליה את המשך השאילתה. אם הטבלה הזו גדולה מדי, זה יהיה צוואר בקבוק משמעותי.
💡 אם נראה בselect_type הרבה ערכים של SUBQUERY או DERIVED, שווה לחשוב: "האם אני יכולה לשכתב את זה ל-JOIN?". ברוב המוחלט של המקרים, JOIN יהיה מהיר ויעיל בהרבה, כי ה-Optimizer יודע לפנות ל Joins בצורה אופטימלית לעומת שאילתות מקוננות.

🚩 דגלים אדומים - מה מעורר חשד? תוצאות בעמודת TYPE

1. ALL (Full Table Scan)
בסיס הנתונים עובר שורה - שורה מתחילת הטבלה ועד סופה כדי למצוא את מה שביקשת. האם זה תמיד גרוע? לא. אם הטבלה קטנה ומכילה 100 שורות, אין שום בעיה. אבל אם הטבלה מכילה 10 מיליון שורות? אנחנו בבעיה קשה והשאילתה תזחל.

2. מלכודת האינדקס המורכב (The Leftmost Prefix Rule)
הרבה מפתחים חושבים שאם הם יצרו אינדקס אחד שמורכב משתי עמודות, למשל (First_Name, Last_Name), הם מכוסים. בפועל, אינדקס מורכב עובד בהקפדה משמאל לימין (כמו ספר טלפונים ישן או רשימת אנשי הקשר בנייד). אם נחפש "כהן, משה", קל מאוד למצוא. אבל נחפש את כל מי שקוראים לו "משה" (בלי לדעת את שם המשפחה),נאלץ לסרוק את כל הספר מתחילתו ועד סופו! סדר העמודות באינדקס הוא קריטי.

3. Using filesort או Using temporary
ה-DB לא הצליח להשתמש באינדקס כדי לשלוף את הנתונים בסדר המבוקש, אז הוא נאלץ ליצור טבלה זמנית בזיכרון (או גרוע מכך, על הדיסק) כדי למיין את התוצאות באופן עצמאי. זהו תהליך יקר ומיותר. במצב כזה נבדוק: האם השאילתה רשומה בצורה תקינה? לעיתים קרובות מדובר בשימוש לא נכון ב-ORDER BY או ב-GROUP BY, או שאין אינדקס שתומך בדרך שבה מיינו את הנתונים.

4. key: NULL
ה-Optimizer החליט שאף אינדקס בטבלה לא רלוונטי לשאילתה שלך (אולי כי יש יותר מדי ערכים זהים, או כי הוא לא בנוי נכון). יש לבדוק את האינדקסים הקיימים או לשקול להוסיף חדשים.

⚠️ מחיר ה INDEX! בכל פעם שנבצע פעולת INSERT, UPDATE או DELETE, בסיס הנתונים לא מעדכן רק את הטבלה עצמה, הוא חייב ללכת ולעדכן גם את כל האינדקסים שקשורים אליה. המשמעות: ככל שיש יותר אינדקסים, פעולות הכתיבה והעדכון יהיו איטיות יותר. צריך למצוא את האיזון העדין.

ריכוז הדגלים האדומים ב-EXPLAIN:

TYPE רמת בהילות מה זה אומר?
ALL 🔴 גבוהה סריקה מלאה של כל הטבלה (Full Table Scan). יש לבדוק אינדקסים בדחיפות!
Using filesort 🟠 בינונית המיון מתבצע בזיכרון באופן עצמאי. פעולה כבדה מאוד שמצריכה בדיקת ORDER BY.
Using temporary 🟠 בינונית נוצרה טבלה זמנית פנימית. שווה לבדוק אופטימיזציה ומעבר ל-JOIN.
key: NULL 🟡 נמוכה השאילתה לא משתמשת באינדקסים בכלל. דורש בדיקה של תנאי ה-WHERE.

אינדקס מכסה (Covering Index / Index Only Scan)

בדרך כלל, ה-DB מוצא את השורה הרלוונטית דרך האינדקס, ואז נאלץ "לקפוץ" לטבלה האמיתית בדיסק כדי לקרוא את שאר העמודות שביקשת ב-SELECT. אבל, אם כל העמודות שביקשת בSELECT נמצאות כבר בתוך האינדקס עצמו (למשל, חיפשת לפי תעודת זהות וביקשת לקבל חזרה רק את תעודת הזהות) ה-DB בכלל לא ייגש לטבלה בדיסק! הוא יחזיר את התשובה ישירות מהאינדקס בזיכרון.

ב-EXPLAIN זה יופיע תחת העמודה Extra בתור Using index (שימו לב!, זה שונה מ-Using index condition). זה המצב האידיאלי, השאילתה שלך פשוט תטוס.

ההבדל בין EXPLAIN ל-EXPLAIN ANALYZE

  • EXPLAIN: תכנון תיאורטי בלבד, הנשען על סטטיסטיקות מוכנות מראש. הוא מהיר במיוחד, בטוח לחלוטין להרצה ולא משנה/משפיע על ה-DB. כלי מושלם להבנת הלוגיקה.
  • EXPLAIN ANALYZE: ביצוע בפועל של השאילתה! ה-DB מקבל פקודה להריץ את השאילתה באמת, ותוך כדי תנועה למדוד במדויק כמה מילישניות לקח כל שלב ושלב.
🚨 אזהרת פרודקשן: מכיוון ש-EXPLAIN ANALYZE מריץ את השאילתה בפועל, הוא לחלוטין לא בטוח להרצה על שאילתות מסוג INSERT, UPDATE או DELETE בסביבת Production! בנוסף, אם מדובר בשאילתת SELECT כבדה מאוד, ה-ANALYZE עלול להעמיס קשות על ה-CPU והדיסק בזמן אמת.
🏠 Back to Orly's Code Corner