אינדקס אחד, כמה עמודות
אינדקס מורכב, שלפעמים נקרא גם אינדקס על כמה עמודות, הוא אינדקס יחיד שנבנה על שתי עמודות או יותר. יוצרים אותו על ידי פירוט העמודות לפי הסדר:
האינדקס idx_orders_customer_status שומר רשומות ממוינות קודם לפי customer_id, ואז לפי status בתוך כל לקוח. הסידור הזה הוא כל הסיפור: כל השאר באינדקסים מורכבים נובע ממנו.
המודל המחשבתי: ספר טלפונים ממוין
דמיינו ספר טלפונים ישן. הרשומות ממוינות לפי שם משפחה, ובתוך כל שם משפחה, לפי שם פרטי. זה בדיוק איך שנראה אינדקס על (last_name, first_name).
חלק מהחיפושים זולים, אחרים לא:
- "מצאו את כל מי ששמו Patel": קל, כל ה-Patel יושבים יחד.
- "מצאו את Priya Patel": קל, קופצים ל-Patel ואז סורקים עד Priya.
- "מצאו את כל מי ששמה Priya": איטי, צריך לסרוק כל עמוד. ה-Priya מפוזרות על פני כל שמות המשפחה.
אינדקס מורכב ב-SQLite עובד באותה דרך. העמודה הראשונה היא מפתח המיון הראשי; העמודה השנייה ממיינת רק רשומות שחולקות את אותו ערך בעמודה הראשונה.
כלל הקידומת השמאלית
SQLite יכולה להשתמש באינדקס מורכב לשאילתה רק כשפסוקית ה-WHERE מגבילה קידומת שמאלית של העמודות שלו. לאינדקס על (a, b, c):
- סינון לפי
a: משתמש באינדקס. - סינון לפי
aו-b: משתמש באינדקס. - סינון לפי
a,bו-c: משתמש באינדקס. - סינון לפי
bלבד, אוcלבד, אוbו-c: האינדקס לא בשימוש.
אפשר לוודא את זה ישירות עם EXPLAIN QUERY PLAN:
התוכנית הראשונה מדווחת SEARCH events USING INDEX idx_events_user_kind_time. השנייה נופלת חזרה ל-SCAN events: סינון לפי kind לבד מדלג על העמודה המובילה user_id, ולכן האינדקס חסר תועלת לשאילתה הזו.
סדר העמודות הוא החלטת תכנון
כיוון שהקידומת השמאלית חשובה, הסדר שבו אתם מפרטים עמודות ב-CREATE INDEX הוא בחירה אמיתית, לא עניין של סגנון. שני כללי אצבע:
- שימו ראשונה את העמודה שאתם מסננים לפיה הכי הרבה. העמודה הזו פותחת את האינדקס למגוון הרחב ביותר של שאילתות.
- שימו עמודות שוויון לפני עמודות טווח. SQLite יכולה לצלול לתוך האינדקס בעזרת
=, ואז לסרוק טווח רציף בעזרת<,>אוBETWEEN, אבל רק על העמודה ה_אחרונה_ שבשימוש.
התוכנית מציגה SEARCH sales USING INDEX idx_sales_region_time (region=? AND sold_at>?). SQLite קופצת ישר ל-region = 'EU', ואז מתקדמת לאורך טווח התאריכים. הפכו את סדר העמודות ל-(sold_at, region), ואותה שאילתה תצטרך לסרוק כל שורה בטווח התאריכים ולבדוק שוב את region בכל אחת.
מורכב מול כמה אינדקסים על עמודה אחת
שאלה נפוצה: ליצור אינדקס אחד על (a, b), או שני אינדקסים נפרדים על a ועל b?
לסינון המשולב, האינדקס המורכב מהיר יותר: SQLite הולכת ישר לרשומות (project_id, state) המתאימות. עם שני אינדקסים על עמודה אחת, SQLite בדרך כלל בוחרת אחד, משתמשת בו כדי לצמצם שורות, ואז בודקת שוב את העמודה השנייה בכל שורה מתאימה. לפעמים היא יכולה לחתוך ביניהם, אבל האינדקס המורכב הוא התשובה הנקייה יותר כשהעמודות נשאלות יחד.
אם project_id ו-state נשאלות גם בנפרד, ייתכן שתרצו את שניהם: את המורכב לסינון המשולב, ובנוסף אינדקס על עמודה אחת על state לשאילתות שמסננות רק לפיה.
Covering Indexes
כשאינדקס כולל כל עמודה ששאילתה צריכה, גם עמודות הסינון וגם העמודות הנבחרות, SQLite יכולה לענות על השאילתה בלי לגעת בטבלה בכלל. זה covering index, וזה המהיר ביותר ששאילתה יכולה להיות.
התוכנית מציגה USING COVERING INDEX idx_invoices_cover. השאילתה קוראת את issued_at ו-total ישירות מהאינדקס: אין צורך ב-notes וב-id, ולכן הטבלה עצמה אף פעם לא נפתחת. הוספת עמודה לאינדקס מורכב רק כדי לכסות שאילתה חמה היא עסקה משתלמת כשהשאילתה הזו רצה כל הזמן.
אילוצי UNIQUE מורכבים
אינדקסים מורכבים גם אוכפים ייחודיות על צירופי עמודות. שימושי כשאף עמודה לא ייחודית בפני עצמה, אבל הצירוף חייב להיות:
ההכנסה השלישית זורקת UNIQUE constraint failed: enrollments.student_id, enrollments.course_id. אותו זוג כבר קיים באינדקס, ולכן SQLite מסרבת לכפילות.
מלכודות שכדאי להכיר
ORבין עמודות שאינן מובילות חוסם את האינדקס.WHERE a = 1 OR b = 2על אינדקס(a, b)בדרך כלל לא יכול להשתמש באינדקס בכלל: SQLite צריכה לשקול כל ענף בנפרד.- פונקציות על עמודות עם אינדקס מנטרלות את האינדקס.
WHERE lower(email) = 'x'לא ישתמש באינדקס עלemail. בנו אינדקס על הביטוי במקום, או נרמלו את הנתונים בזמן ההכנסה. - אינדקסים לא באים בחינם. כל אינדקס מתעדכן בכל
INSERT,UPDATE(של עמודות באינדקס) ו-DELETE. שלושה אינדקסים מורכבים על טבלה עם הרבה כתיבות יכולים להשתלט על עלות הכתיבה. - הריצו
ANALYZEאחרי בניית אינדקסים. המתכנן של SQLite משתמש בסטטיסטיקות ש-ANALYZEאוסף כדי לבחור בין אינדקסים מועמדים. בלי הסטטיסטיקות האלה, הוא נופל חזרה להיוריסטיקות שלא תמיד אופטימליות.
תהליך עבודה מעשי
כשמכווננים שאילתה איטית, הלולאה נראית בדרך כלל כך:
- הריצו
EXPLAIN QUERY PLANעל השאילתה כדי לראות מה SQLite עושה היום. - אם היא סורקת, הסתכלו על פסוקית ה-
WHERE: מה עמודת השוויון? מה עמודת הטווח? מה נבחר? - בנו אינדקס מורכב שמסודר קודם לפי שוויון ואחר כך לפי טווח, והוסיפו בסוף את העמודות הנבחרות אם כיסוי עוזר.
- הריצו
ANALYZE. - הריצו שוב
EXPLAIN QUERY PLAN. ודאו שהתוכנית השתנתה ושהאינדקס בשימוש. - מדדו את זמן השאילתה לפני ואחרי על נתונים מייצגים.
דלגו על שלב 6 על אחריותכם. אינדקס ש_נראה_ נכון בתוכנית עדיין יכול להיות איטי יותר בפועל אם הטבלה קטנה או אם המתכנן בוחר נתיב אחר.
הבא בתור: אינדקסים חלקיים
אינדקסים מורכבים מכסים כל שורה בטבלה. אבל לעיתים קרובות רק תת קבוצה קטנה של שורות חשובה: פניות פתוחות, משימות שלא עובדו, רשומות שלא נמחקו. אינדקס חלקי מאפשר לבנות אינדקס רק על השורות האלה, עם פסוקית WHERE שאפויה לתוך האינדקס עצמו. זה העמוד הבא.
שאלות נפוצות
מהו אינדקס מורכב ב-SQLite?
אינדקס מורכב הוא אינדקס יחיד שמכסה שתי עמודות או יותר. יוצרים אותו עם CREATE INDEX idx_name ON table(col_a, col_b). SQLite שומרת את הרשומות ממוינות קודם לפי col_a, ואז לפי col_b בתוך כל ערך של col_a, כמו ספר טלפונים שממוין לפי שם משפחה ואז לפי שם פרטי.
האם סדר העמודות חשוב באינדקס מורכב ב-SQLite?
כן, מאוד. SQLite יכולה להשתמש באינדקס מורכב לשאילתה רק אם פסוקית ה-WHERE מסננת לפי קידומת שמאלית של העמודות באינדקס. אינדקס על (a, b, c) עוזר לשאילתות שמסננות לפי a, לפי a ו-b, או לפי שלושתן, אבל הוא לא יכול לעזור לשאילתה שמסננת רק לפי b או רק לפי c.
מתי כדאי להשתמש באינדקס מורכב במקום באינדקסים נפרדים על עמודה אחת?
השתמשו באינדקס מורכב כששאילתות מסננות או ממיינות באופן קבוע לפי אותו צירוף עמודות יחד. אינדקסים נפרדים על עמודה אחת מתאימים כשכל עמודה נשאלת בנפרד. הריצו EXPLAIN QUERY PLAN כדי לראות באיזה אינדקס SQLite באמת בוחרת: זה המשוב האמין היחיד.
מהו covering index ב-SQLite?
covering index כולל כל עמודה שהשאילתה צריכה, כך ש-SQLite יכולה לענות על השאילתה ישירות מהאינדקס בלי לגעת בטבלה. EXPLAIN QUERY PLAN מציג USING COVERING INDEX כשזה קורה. הוספת עמודות לאינדקס מורכב רק כדי לכסות שאילתה חמה היא אופטימיזציה נפוצה.