הקדמה
במאמר זה נדבר בקצרה של מנגנון שמנהל את ה- buffer cache. חשוב לי להדגיש שמכיוון שהנושא הזה מורכב ואני רוצה לדבר רק על אספקט אחד שלו (full scans והאפשרות של cache), אני מפשט מאד את ההסבר. לכן יש כאן אי דיוקים מכוונים כדי להסביר את הנושא בצורה ברורה מצד אחד, אך לא "כבדה" מדי מצד שני. בהמשך נפרסם באתר מאמר שמדבר על המנגנון בצורה מלאה ומדוייקת יותר.
ניהול ה- buffer cache
ה- buffer cache באורקל נועד כדי לשמור בלוקים של מידע בזכרון ולחסוך גישה לדיסק. ה- buffer cache עובד עם מנגנון LRU (least recently used). על מנת לפשט את המנגנון, נגיד רק שאורקל מעלה בלוק של מידע ל- buffer cache כשקוראים את הבלוק בפעם הראשונה, ושומר לעצמו מידע על מתי וכמה פעמים השתמשו בבלוק הזה. כאשר צריך לפנות בלוק מהזכרון כדי להעלות בלוק חדש, אורקל יפנה בלוק שלא השתמשו בו לאחרונה וסך הכל השתמשו בו מעט פעמים.
מה קורה ב- full scan?
כל זה טוב ויפה, אבל כאשר משתמשים במנגנון הזה יש לנו בעיה. לפעמים אורקל מבצע full table scan, פעולה זו עוברת על כל הבלוקים של טבלה שלמה לצורך ביצוע שאילתה או פעולה אחרת. לפי המנגנון שתארנו למעלה, ביצוע full table scan על טבלה גדולה יגרום בפועל לעליה של כל הטבלה ל- buffer cache ודריסה של רוב או כל הזכרון.
כדי להתמודד עם הבעיה הזאת, פעולות של קריאת טבלה שלמה יתנהגו בצורה אחרת מול הזכרון. אורקל יעלה מספר בלוקים לזכרון (מספר הבלוקים נקבע על ידי הפרמטר db_file_multiblock_read_count) ובלוקים אלו יסומנו מיד כבלוקים "ישנים" שניתן לדרוס אותם. תהליך זה יגרום לבלוקים האלה להתפנות מהר מאד מהזכרון ולמנוע מצב שבו הבלוקים של טבלה אחת ממלאים את כל ה- buffer cache תוך כדי דריסה של בלוקים שאנחנו רוצים שיהיו בזכרון.
שימו לב שאם אתם מסתכלים על סטטיסטיקות של שאילתות שמבצעות full table scan בדרך כלל תראו physical reads, כלומר קריאה מהדיסק, גם אם מריצים את אותה שאילתה שוב ושוב והכל אמור להיות בזכרון. עכשיו אתם מבינים את הסיבה, אורקל משתמש שוב ושוב באותם מקומות בזכרון ולכן תמיד יתבצעו קריאות מהדיסק (ראו דוגמא בסוף המאמר).
ואם בכל זאת אנחנו רוצים להעלות טבלה שלמה לזכרון?
לאחר שהבנו מה אורקל עושה עם full scan, נראה שאם אנחנו ממש רוצים, אנחנו יכולים לגרום לו להתנהג אחרת, ולקרוא טבלאות שלמות לזכרון בלי לזרוק את הבלוקים מיד החוצה, מה שיגרום לכל או רוב הטבלה להישאר בזכרון. דבר זה יעיל במיוחד עבור טבלאות קטנות יחסית שמבצעים עליהן הרבה פעמים full table scan, אך גם יכול לשמש במקרים אחרים בהם יש הרבה זכרון מוקצה ל- Oracle וטבלה שנדרש לעשות עליה full table scan הרבה פעמים.
כדי לעשות את זה קיימות שתי אפשרויות:
- הגדרת הטבלה כ- cached על ידי הפקודה:
Alter table my_table cache
- שימוש ב- hint הנקרא cache בשאילתה עצמה:
Select /*+ cache(my_table) */ * from my_table
דוגמא
יצרתי טבלה בשם my_table שמכילה 2 עמודות (אחת מהם היא מספרית בשם f1) והרבה מאד רשומות.
כל הרצה של השאילתה select sum(f1) from my_table מבצעת קריאות מהדיסק, אולם אחרי שימוש ב- cache אין יותר קריאות מהדיסק. שימו לב כמובן שבפעם הראשונה שמריצים את השאילתה עם cache עדיין יש קריאות מהדיסק, אבל ההרצה הזאת גורמת לבלוקים להישאר בזכרון ולכן הריצה הבאה כבר לא תקרא מהדיסק אלא רק מהזכרון.
שימו לב לשורה של ה- Physical reads בסטטיסטיקות של השליפות
SQL> set autotrace traceonly stat SQL> select sum(f1) from my_table; Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 17248 consistent gets 16006 physical reads 0 redo size 413 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
SQL> / Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 17248 consistent gets 16000 physical reads 0 redo size 413 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
SQL> select /*+ cache */ sum(f1) from my_table; Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 17248 consistent gets 16268 physical reads 0 redo size 413 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
SQL> / Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 17248 consistent gets 0 physical reads 0 redo size 413 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
Cache Hint
ניתן ליצור קשר עם לירון דרך עמוד האודות שלו: אודות
Latest posts by לירון אמיצי (see all)
- ה-SQL Performance Analyzer ב-Real Application Testing - 18/12/2014
- עד כמה דינאמי יכול להיות SQL דינאמי? - 02/10/2014
- מבוא ל- Real Application Testing - 23/07/2014
5 תגובות ל- “The Buffer Cache and Full Scans”
[…] תודה לכל משתתפי האתגר, מחר נפרסם את אתגר מאי. יש למה לחכות! מי שמעוניין לקבל מושג לגבי אופי האתגר שיפורסם מחר, מוזמן להיכנס למאמר הבא של לירון אמיצי: http://www.ildba.co.il/magazine/buffer-cache-and-full-scans/ […]
[…] כשמבצעים full table scan, אורקל אינו מעלה את כל הטבלה לזכרון ובכך דורס את מה שיש כבר ב- buffer cache, אלא מעלה כל פעם מספר בלוקים ומיד מוציא אותם מהזכרון ומעלה במקומם בלוקים אחרים. אם אתם לא מכירים את המנגנון, אתם מוזמנים לקרוא עליו במאמר שפורסם לאחרונה באתר: http://www.ildba.co.il/magazine/buffer-cache-and-full-scans. […]
אחלה מאמר,
לדעתי צריך גם להוסיף שבבסיס נתונים מבצעי צריך להמנע ככל האפשר מ FULL TABLE SCANS
בייחוד בטבלאות גדולות שבהן גם ה IO הראשוני יהיה גבוה
ואם הטבלה היא CACHED אז בכלל אכלנו אותה 🙂
הי ליאור,
קודם כל תודה.
לגבי ההערה שלך, זה לא מדויק, הכל תלוי בסוג בסיס הנתונים שאנחנו מדברים עליו. בסביבות DWH למשל, דווקא נרצה לראות מקרים של full scan בדוחות שעוברים על מידע של טבלאות שלמות. את זה עושים עם storage חזק וב- parallel. לגבי מערכות OLTP, אני בהחלט מסכים.
לירון
[…] תודה לכל משתתפי האתגר, מחר נפרסם את אתגר מאי. יש למה לחכות! מי שמעוניין לקבל מושג לגבי אופי האתגר שיפורסם מחר, מוזמן להיכנס למאמר הבא של לירון אמיצי: http://www.ildba.co.il/magazine/buffer-cache-and-full-scans/ […]
השאר תגובה: