2018-08-12

נושאים מתקדמים ב MySQL: חלק ג׳ - מנועי אחסון, ומבנה האינדקסים

פוסטים בסדרה:
"תסביר לי" - גרסת ה SQL
לקחת את הביצועים ברצינות, בעזרת MySQL Performance Schema
נושאים מתקדמים ב MySQL: חלק א' - עבודה עם JSON
נושאים מתקדמים ב MySQL: חלק ב' - json_each  ו Generated Columns
נושאים מתקדמים ב MySQL: חלק ג׳ - מנועי אחסון, ומבנה האינדקסים

----

אלמנט חשוב של הארכיטקטורה של MySql היא ההפרדה של ה Storage Engines כרכיב מודולרי בבסיס הנתונים.
זהו בעצם יישום של דפוס Adapter בו ניהול האחסון לדיסק (או מקור אחר) מבוצע ע"י מודול שניתן להחליף.

מכיוון שהשמירה לדיסק היא מרכיב מרכזי של בסיס הנתונים, למנוע האחסון (Storage Engine) יש משמעות מכרעת על העבודה של בסיס הנתונים, והתנהגותו. מנועי אחסון שונים עובדים במקביל, והבחירה במנוע אחסון היא ברזולוציה של טבלה.

כדי לסבר את האוזן, הנה רשימה של כמה מנועי-אחסון בהם נעשה שימוש ב MySQL:
  • InnoDB - ברירת המחדל של MySQL מאז גרסה 5.5, וכיום גם ברירת המחדל של MariaDB. נדבר עליו בהמשך.
  • MyISAM - ברירת המחדל של MySQL לפני גרסה 5.5. נדבר עליו בהמשך.
  • Memory (או Heap) - אחסון של הנתונים בזיכרון.  הגישה מהירה, אך באתחול בסיס הנתונים - הסכמה נשמרת, בעוד המידע בטבלאות מתאפס.
  • CSV - אכסון וניהול המידע בקבצי CSV.
  • BlackHole - כמו dev/null/ - המנוע מקבל שאילתות עדכון - אך לא מאחסן מידע בכלל. השימוש הנפוץ במנוע הזה הוא בתצורה מבוזרת בה כל המידע שנשמר ל node משוכפל ל replica מרוחקת, ואין צורך לשמור אותה מקומית.
  • Archive - מנוע ש optimized לגישות נדירות לכמות גדולה של נתונים בכל פעם. למשל: Audit.
  • XtraDB - מנוע בסיס נתונים משופר שנבנה ע"י חברת Percona (חברת ייעוץ / מומחים ל MySQL). תקופה מסוימת נחשב עדיף על InnoDB בביצועים והיה מנוע ברירת המחדל של MariaDB (החליף את Aria), אך לאחרונה הפערים נסגרו - ומנוע ברירת המחדל של MariaDB כיום גם הוא InnoDB.
  • MyRocks - מנוע שפותח ע"י פייסבוק המאפשר להשתמש בנתונים של RocksDB (שהוא בעצם Fork של LevelDB שמתוחזק ע"י פייסבוק). המנוע נכלל בהתקנה הגרסאות החדשות של MariaDB, וגם בהתקנה של Percona Server (ה distro של חברת Percona ל MySQL).
  • TukoDB - עוד מנוע שנוצר ע"י חברת Percona וזמין כברירת מחדל ב MariaDB וב Percona Server, המכוון לטיפול במידע שהוא Steaming או שיש לטפל בו ב Near-Realtime. המנוע משתמש באינדקסים המבוססים על מבנה-נתונים בשם Fractal Tree במקום ה B-Tree המסורתי.


מנועי האחסון הם Pluggable וניתן להתקין אותם על גבי התקנה קיימת של MySQL.
הבחירה הארכיטקטונית של MySQL במנוע אחסון שהוא Pluggble פותחת אופציה להוסיף יכולות, בקלות יחסית, לבסיס הנתונים וגם לבצע שינויים הדרגתיים בארכיטקטורה (נבנה את InnoDB לאורך שנים - עד שיהיה בשל להיות ה Default). מצד שני - הגישה הזו מקשה על אופטימיזציות קצה-אל-קצה ברמת בסיס-הנתונים כולו, כי כל מנוע אחסון מתנהג קצת אחרת.
כמו כל שיקול ארכיטקטוני - יש פה Trade-off.


מקור. הבהרה: Keys Cache היא יכולת ש MyISAM משתמש בה - ולא יכולת של השרת המתבססת על MyISAM.


המנועים המרכזיים: InnoDB מול MyISAM


אני אעשה את הדיון הזה קצר ופשוט. הנה השוואה של התכונות העיקריות בין המנועים:


כיום, InnoDB עולה בכמעט כל פרמטר על MyISAM.
בעבר עוד היו ל MyISAM יתרונות יחסיים, כגון דחיסה, Full-Text Index, או אינדקס Geospatial.
הפערים הללו נסגרו, ו MyISAM נראה היום מיושן למדי (אין Transactions! הנעילה היא ברמת הטבלה!).
עד שנת 2009, בערך, MySQL פיגר ביכולות בסיסיות אחרי שאר התעשייה. הוא היה חינמי ופשוט - וכך הצליח לחדור ולתפוס נתח שוק משמעותי.

למנוע ה Memory, כמובן, אין תחליף. בסיס הנתונים משתמש בו לכל מיני טבלאות מערכת (לדוגמה: ה Performance Schema - כך שה overhead שלה יהיה זניח למדי), ואכסון נתונים בזיכרון היא יכולת שימושית במגוון מקרים.

שאר המנועים שציינתי, הם הרבה פחות נפוצים בשימוש. אני אישית, מעולם לא עבדתי עם מנוע שהוא לא אחד מהשלושה בטבלה למעלה.



InnoDB



המבנה המדובר ביותר לאינדקסים של בסיס נתונים הוא מבנה הנתונים בשם B-Tree. אני מניח שהמבנה הזה מוכר לכם מהתואר האקדמי או מקור אחר. זהו מבנה של עץ שבו כל node הוא בגודל של Page בדיסק, כך שמצמצמים את מספר הקריאות לדיסק. הרעיון תקף גם לבלוקים של זיכרון (הרי הזיכרון הוא לא באמת "Random Access". גם שם ניגשים לבלוקים).

מבנה הנתונים ש InnoDB משתמש בו הוא גרסה מעט "משופרת" הנקראת B+Tree (בחירה נפוצה בקרב בבסיסי-נתונים):
  • כל Leaf node מכיל מצביע לזה שאחריו (על מנת ליעל סריקות של טווחים).
  • כל הערכים נשמרים רק ב Leaf Nodes מה שאומר שב nodes הביניים יש רק מפתחות ולא ערכים. זה טוב כי אז ניתן לשמור יותר מצביעים בכל node ביניים. מצד שני, מאבדים את היכולת לשים ב node ביניים ערכים (key+value) שבשימוש נפוץ, ואז להגיע אליהם בפחות גישות. כרגיל: a trade-off.
מקור: Stack Overflow


MySQL מנהל לכל טבלה שני סוגי אינדקסים:
  • Clustered Index, או Primary Index - בו מאחסנים גם מפתחות (keys) וגם את שאר ערכי הטבלה (row) ביחד, כאשר הערכים בעלי ערך אינדקס דומה/עוקב - מאוחסנים זה ליד זה פיסית על הדיסק.
    • לכל טבלה יש רק Primary Index אחד.
    • אם לא תגדירו Primary Index לטבלה, אזי InnoDB ייצור אחד לבד, על בסיס auto-increment, אבל שלא חשוף לכם. יש בזה כמה בעיות - וההמלצה הגורפת היא תמיד להגדיר Primary Index בעצמכם.
  • Non-Clustered Index או Secondary Index - בו יושבים keys, אך הערכים הם לא ה Rows עצמם, אלא מצביעים ל primary Index. 
    • האינדקס ממוין ע"פ ה keys, ולא ע"פ ה primary Index.
    • אפשר להגדיר כמה secondary Indexes שרוצים לכל טבלה.



בואו נראה תרשים שיסביר זאת בצורה יותר ברורה:

מקור: סיני כלשהו. אהבתי את התרשים.

  • ה Primary Index הוא B+Tree, כאשר בכל Leaf Node מאוחסנים <Pair<Key, Row. הרשומה שלנו במקרה הזה היא מס' חברה ושם (אם מתעלמים מה key).
  • על הדיסק נשמרים הערכים בצורה ממוינת. InnoDB ינסה לשמור את ה Pages של ה LeafNodes העוקבים קרובים זה לזה על הדיסק (כדי שיהיה ניתן לקרוא אותם בגישה רציפה אחת).
  • המחיר של Clustered Index הוא בהכנסת רשומות (או בטווח הפחות מיידי - מחיקות). הפעולות הללו יהיו יקרות יחסית ל non-clustered index.
    • פעולות של פיצול / איחוד דפים על הדיסק - הן יקרות.
    • כש InnoDB יוצר Page חדש, הוא מותיר בו 7% שטח פנוי, עבור עדכונים של רשומות (נניח ערך varchar שגדל) או הכנסה של רשומות חדשות.


עכשיו נתבונן על ה secondary index:


  • ה Secondary Index הוא גם B+Tree, כאשר בכל Lead Node מאוחסנים <Pair<Key, Primary Key.

מה שחשוב להבין מזה:
  • הרשומות בטבלה אשכרה נשמרות בדיסק ממוינות ע"פ ה Primary Index. יש לזה מחיר - אבל גישות ע"פ ה Primary Index יהיו יעילות ביותר.
    • לדייק: הרשומות בטבלה נשמרות על ה Primary Index.
    • זה לא בהכרח המצב בבסיסי נתונים רבים אחרים. אין clustered index ב Postgres או MyISAM Engine, ובאורקל זהו פיצ'ר אופציונלי (Index-Organized Tables).
  • אינדקסים משניים הם רק הצבעות ל Primary Index. 
    • ככל שה Primary Key הוא ארוך יותר (בבתים) - אזי כל ה secondary index המצביעים אליו יהיו קטנים יותר, ויוכלו להכיל פחות רשומות בכל Leaf Node. הסבר: גודל ה Leaf Node הוא קבוע. למשל 16KB או 64KB.


עוד אינדקסים שכדאי להכיר:
  • Full Text Index - סוג של אינדקס הפוך המכיל את כל ההצבעות לרשומות המכילות מילות מפתח מסוימות. זהו אינדקס גדול מאוד - אך יכול לשפר מאוד חיפושים ע"פ מילות מפתח.
    • בכדי להשתמש בו, יש להשתמש בפקודת MATCH AGAINST במקום ב WHERE.
  • Geospatial Index - לחיפוש בשטחים (למשל: פוליגונים) על גבי מרחב דו-מימדי (מרחב גאוגרפי). המימוש של InnoDB הוא של R-Tree, אם כי גם KD-Tree הוא מבנה מקובל לאינדקסים מסוג זה.
    • שווה לציין שהמימוש של InnoDB ל Geospatial Index הוא מוגבל יחסית למימושים של Oracle, PostgreSQL או MongoDB.




האינדקסים הטובים ביותר ע"פ גוגל
שניה! .... מה?!?!


איך להגדיר את ה Primary Index האולטימטיבי?


סיכמנו שלתת ל InnoDB לקבוע לבד את ה Primary Index הוא פרקטיקה לא טובה.
בואו נראה מהן האופציות המקובלות / ה Best-Practices:
  • אפשר להשתמש ב Auto-Increment (גודל ברירת מחדל = 4 בתים).
    • יתרון: Primary Index מספרי הוא קטן (= מעט בתים בזיכרון) ויאפשר להכניס:
      • יותר מצביעים ב Primary Index intermediate nodes = פחות גישות לדיסק.
      • יותר רשומות ב Secondary Index Leaf Nodes = אינדקסים משניים יותר קטנים ויעילים.
      • יתפוס פחות מקום כ Foreign Key המצביע על הטבלה.
    • יתרון נוסף: ניתן למיין בזריזות את הטבלה ע"פ סדר עולה / יורד של הכנסת הרשומות. זה נחמד בעיקר בעבודה עם כלי Queries נוסח SQL Pro.
    • יתרון נוסף: מפתח קטן יקל על פעולות Join (החלק שמתבצע בזיכרון).
  • אפשר להשתמש ב GUID (כלומר = 128 ביט).
    • למרות ש 128, באופן תאורטי, הם 16 בתים, לרוב נייצג את ה GUID בייצוג הקסדצימלי (0-9A-F) מה שאומר מחרוזת באורך 32 בתים, ואז נגדיר אותו כ (varchar(32 - מה שבמצבים מסוימים עשוי להתפרש ע"י MySQL כ 3 תווים לכל אות + תו delimiter (בגלל ה Varchar) = אורך של 97 בתים.
      • מתוך רצון להטיב, אפשר להגדיר את השדה כ (char(32 - מה שיגרום לבסיס הנתונים להקצות לו 96 תווים (utf8mb3) או 128 בתים (utf8mb4) - כמעט תמיד.
      • יש ב MySQL 8 אופטימיזציות חדשות שיכולות להטיב את המצב. בלתי אפשרי באמת להעריך מה תהיה התוצאה המדויקת של כלל האופטימיזציות שעובדות בשילוב.
    • ההבדל בין 4 בתים ל 97 בתים, או אפילו "רק" 32 בתים - הוא כבר משמעותי למדי!
    • יתרון: ה ID הוא ייחודי בכל המערכת (או כל מערכת). באגים בהם משתמשים ב key הלא נכון - ייחשפו במהרה. ב auto-increment, אם השתמשנו במפתח לא נכון - יש סיכוי טוב שנקבל רשומה לא נכונה ויהיה קשה יותר לגלות זאת.
      • הייחודיות הזו מאפשרת לאחד נתונים מגרסאות שונות של בסיס הנתונים. למשל: תסריט של recovery, תסריט של Multi-region או כמה עותקים של בסיס הנתונים.
      • Id ייחודי ובעל פיזור אחיד יחסית, מאפשר Sharding (תסריט פחות נפוץ).
    • יתרון: אבטחה. מישהו שנחשף למפתח אחד - לא יכול להסיק ממנו ולנחש מפתחות אחרים. ב Auto-increment אפשר בקלות להבין שיש מפתחות דומים במספרים עוקבים.
    • חיסרון: האקראיות של המספרים הופכת את המיון של ה Clustered index לחסר משמעות.
      למשל: ב MS-SQL יש פונקציה בשם ()newsequentialid, המייצרת GUID בעל אלמנטים סדרתיים - כך שעדיין ה clustered index בא לידי ביטוי.
    • שווה לציין גם ש GUID עצמו לא כולו אקראי. חלק ממנו מבוסס על הפרטים של המכונה המקומית (למשל IP address), כך שאם כל ה GUID נוצרים על אותה המכונה (בסיס הנתונים) - יש כאן חוסר יעילות מסוים. זה עדיין משני לכל הנ"ל.
  • אפשר להשתמש במפתח עסקי טהור. למשל: כתובת אימייל. שם חברה + קוד מדינה (אם ייחודי), וכו'
    • המפתח הזה עלול להיות הגדול ביותר = ההשפעה החמורה ביותר על הביצועים מהיבט גודל האינדקסים.
    • מצד שני, אם יש לנו intensive read workload שניתן לאפיין בצורה ברורה (נניח: קריאת כל הרשומות של אותו ה email בצורה תדירה) - אזי מפתח שירכז את כל הרשומות הללו במספר קטן של דפים בדיסק עשוי לשפר מאוד את הביצועים.
    • קריאה של 10 דפים בכדי לטעון 1000 רשומות תהיה מהירה בסדרי גודל מקריאה של 300 דפים בכדי לטעון את אותן 1000 רשומות. זה כבר לא עניין של אינדקס - אלא גישה לנתונים בדיסק.

טוב. לא נפתור כאן את הדילמה הזו, דילמה רבת שנים. בכל זאת, כמה תובנות מצדי:
  • GUID הוא בחירה טובה, כאשר מדובר בטבלאות לא גדולות במיוחד ו/או אינן מעורבות בעבודה אינטנסיבית (הרבה חיפושים מורכבים, הרבה joins, וכו').
  • כאשר הביצועים מתחילים לשחק תפקיד - קרנו של ה Auto-Increment עולה.
    • פשרה אפשרית היא להחזיק שני מפתחות לרשומה:
      גם Id כ auto-Increment (שזה יהיה ה Primary Key) וגם GUID כשדה נוסף בטבלה (שיוחזק כ Secondary Key). 
      • כל חשיפה לעולם החיצון (למשל: Clients או בסיס נתונים אחר) - תתבצע על בסיס ה GUID.
      • כל העבודה הפנימית - תתבצע על בסיס Auto-increment.
    • בכל מקרה, דאגו להחזיק את ה GUID כ 16 בתים, ולא כ 97. ההבדל בביצועים עשוי להיות דרמטי.
      • הקצרנים, יוכלו לקצץ חלקים לא אקראיים, ולקצר את המפתח ל 12 או 14 בתים. זה כבר לא כ"כ משמעותי. דווקא להוספת כמה בתים בתחילת ה GUID המאפשרים סדרתיות (למשל - מספר build או מספר טעינה של השרת) - תהיה השפעה טובה יותר.
  • מפתח עסקי טהור, הוא לא בהכרח רע.
    • יש פוטנציאל טוב לשיפור אמיתי, אם יודעים מה עושים ומהם דפוסי השאילתות.
    • דיי נפוץ שדפוסי השימוש משתנים עם הזמן, ובצוות יש אנשים בעלי הבנה פחות עמוקה של ה tradeoffs וההשלכה שלהם, כך שסטטיסטית מפתחות טהורים עסקים - נוטים להיות פחות טובים.
      • לא הזכרנו את המקרה שבו מטעמים עסקיים, אנחנו נדרשים לעקוף את האילוץ של המפתח (למשל: התמיכה רוצה להזין למערכת משתמש שאין להם כתובת אימייל שלו עדיין).
    • ולכן, ככלל, הייתי ממליץ להימנע ממפתחות עסקיים טהורים, מלבד מקרים חריגים. הם בעייתיים ברמת ה Scalability של הצוות.


הערה על Prefix Indexes:

ב MySQL יש יכולת להגדיר אינדקסים על חלק מהשדה, למשל: 10 תווים ראשונים. למשל:

CREATE INDEX part_of_name ON customer (name(10)); 

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

אליה וקוץ בה: prefix Index הוא אינו Covering, כלומר - באינדקס לא מאוחסן הערך השלם של העמודה.
בד"כ במפתח משני ה key הוא ערך השדה. בשאילות מסוימות - מספיק לקרוא את האינדקס המשני מבלי לקרוא בכלל את ה primary index (או "הטבלה").
ב Prefix Index - תמיד MySQL ילך לקרוא את ה Primary index. גם אם ה significant part נראה נכון. היה ניתן לבצע אופטימיזציות ובמקרים מסוימים לא ללכת. למשל: שאילתה המבוססת על התנאי '%name LIKE 'Lio לא צריכה באמת ללכת לאינדקס הראשי, כי יש לה את כל המידע הנדרש ב Prefix Index.
כיום - אין כזו אופטימיזציה ו MySQL תמיד יקרא גם את ה Primary Index.

לסיכום: Prefix Index נשמעים רעיון טוב, אבל הרבה פעמים הם עובדים פחות טוב מהמצופה מכיוון שהם לא Covering.




הארכיטקטורה של InnoDB


תחזוקה של אינדקסים: Analyze Table


מנוע בסיס הנתונים הוא יישות חכמה, שיודע לנהל את האינדקסים בצורה אופטימלית ללא מגע יד אדם.
נכון? - לא.
בתקופה המודרנית אנו נוטים לחשוב שמה שאפשרי מבחינת תאורטית הוא באמת מה שקורה בפועל. הרבה עבודה נדרשת בכדי לגרום למשהו לעבוד בצורה "נבונה" - והרבה פעמים אנו צפויים להתאכזב.

ל MySQL (ובכלל בסיסי נתונים) כן יש כמה מנגנונים לתחזוקת אינדקסים - אבל התערבות ידנית עשויה להיות שימושית למדי.

תפקיד ה Query Optimizer של MySQL הוא לבנות את תוכנית הפעולה (להלן Query Plan) היעילה ביותר על מנת לספק שאילתה נתונה. נקודת מפתח בתוכנית הזו היא האם להשתמש באינדקסים - ואלו אינדקסים.

ההחלטה מבוססת על חישוב עלות-מוערכת, כאשר שאלת המפתח היא איזה סדר פעולות יוביל אותנו לפחות גישה לדיסק וסריקה של פחות נתונים בזיכרון (וגם פחות טבלאות זמניות, filesort, וכו'). ההחלטה מבוססת על סטטיסטיקות של בסיס הנתונים אודות הטבלאות המעורבות:
  • כמה רשומות יש בטבלה, ומה הפיזור שלהן (להלן Cardinality).
  • כמה רשומות צפויות להיות בטווח מסוים של מפתחות (להלן: ()records_in_range)

ספציפית, InnoDB שומר את הסטטיסטיקות הנ"ל בטבלה שלו המבוססת על ה Memory Storage Engine.
פעולת איסוף הסטטיסטיקות מתבצע ע"י דגימה של מספר קטן של דפים (ברירת מחדל = 8 דפים, המספר שיבחר בסוף מושפע גם מגודל הטבלה) שנבחרו באופן אקראי מתוך הטבלה, ומתוך הנחה שהדגימה הזו מייצגת.

הדגימה הזו תתבצע במצבים הבאים:
  • גישה ראשונה לטבלה. 
  • מספר הרשומות בטבלה גדל ב 10% מאז הדגימה האחרונה או 2 מיליארד רשומות נוספו מאז הדגימה האחרונה (לטבלאות גדולות במיוחד).

תובנה חשובה היא שהסטטיסטיקות אינן מדויקות, ואינן בהכרח עקביות. כל ניתוח מחדש של הטבלה עשוי להציג תמונה מעט שונה.

ניתן להורות ל DB לבצע את הדגימה מחדש ע"י הפקודה:

ANALYZE TABLE table_name; 

הפעולה הזו קצרה מאוד [א], מכיוון שהיא ניגשת למדגם קטן למדי של נתונים בטבלה.
ניתן לקבוע את גודל המדגם ע"י הפרמטר בשם innodb_stats_transient_sample_pages. ערך גבוה יותר יספק סטטיסטיקות מדויקות יותר, במחיר פעולת ניתוח יקרה יותר. נראה, למשל, שב AWS RDS הערך נקבע ל 20.

מתי חשוב לבצע Analyze Table בצורה יזומה?
אחר שינוי אינדקס, או הוספה של כמות גדולה מאוד של נתונים לטבלה - כדאי לבצע פעולת Analyze Table יזומה. כבר נתקלתי במצבים בהם ביצענו שינויים שאמורים היו להיות משמעותיים לביצועים - אך לא ראינו את ההשפעה שלהם עד הפעלה של פעולת ה Analyze Table

החל מגרסה 5.6.2 ברירת המחדל היא לשמור את הסטטיסטיקות לדיסק בעת אתחול (1 = innodb_stats_persistent). המוטיבציה המוצהרת לשינוי היא שמירה על עקביות של זמני הריצה של השאילתות לאחר אתחול של השרת. ייתכן וזה היה בשל משתמשים שפתחו באגים בנוסח "שאילתה X יותר אטית לאחר עדכון ו/או Restart" ולא אופטימיזציה נכונה לכל מצב.

החל מגרסה 8.0.3 נוסף הפרמטר sysvar_information_schema_stats_expiry המוחק את הסטטיסטיקות כל זמן נתון. ערך ברירת המחדל הוא 24 שעות. זו גישה הפוכה, והרבה יותר הגיונית, לדעתי.
אני לא מכיר דרך פשוטה לבצע Analyze Table תקופתי בגרסה 5.7 (ועדיף: בזמן ה off hours של המערכת). זו פעולה הגיונית מאוד, ובד"כ נעשית ע"י custom scripts.

ניתן לבדוק מתי התעדכנו הסטטיסטיקות לאחרונה ע"י בדיקת עמודת ה last_update בטבלת mysql.innodb_table_stats ובפירוט של אינדקס בטבלת ה mysql.innodb_index_stats.





תחזוקת אינדקסים: Optimize Table


פעולת התחזוקה החשובה השנייה היא פעולה של Defragmentation של מבנה הטבלה (האינדקס הראשי) והאינדקסים המשניים.
השפעה שלילית גדולה של fragmentation היא כאשר עושים סריקה סדרתית של אינדקס / נתוני הטבלה. יש הבדל מהותי אם זו קריאה רציפה מהדיסק (ועדיף: של דפים עם תפוסה גבוהה), או קריאה של דפים המפוזרים במקומות שונים על הדיסק. גם בעידן ה SSD - ההבדל הוא מהותי מאוד.

הפקודה Optimize Table שקולה ל:
  • איחוי ה primary index. 
    • כלומר: דחיסת הדפים ל 93% תפוסה, וכתיבה סדרתית שלהם על הדיסק.
  • איחוי ה secondary indexes
    • כנ"ל.
  • ביצוע ANALYZE TABLE על הטבלה

ב InnoDB, הפקודה OPTIMIZE TABLE ממופה ל ALTER TABLE ... FORCE - המבצעת פעולה דומה.
מתחת לקלעים InnoDB ייצור עותק נוסף של הטבלה אשר יכתב לדיסק בצורה רציפה (זהו האיחוי), יעדכן בו שינויים שנעשים בטבלה תוך כדי העבודה, ואז יחליף את העותק הישן בחדש.
  • הפעולה תנעל את הטבלה לזמן קצר (שלב ה preparation) ואז עלולה לארוך זמן לא-מבוטל (מספר שעות הוא לא זמן נדיר לטבלה גדולה / פעילה. במקרים קיצוניים זה גם יכול לארוך ימים).
  • אין אינדיקציה על התקדמות, וביטול הפעולה גם יכול לארוך זמן.
  • בזמן הזה, כל העבודה עם הטבלה תהיה אטית יותר. אם זו טבלה מרכזית - ההשפעה על כלל בסיס הנתונים עשויה להיות ניכרת. חשוב מאוד לבצע פעולות OPTIMIZE TABLE ב Off Hours של המערכת שלכם.
  • כשאפשר - InnoDB ישתמש ביכולת בשם online DDL על מנת לבצע את העדכונים הללו במקביל, ובצורה שתעמיס פחות על הטבלה המקורית.
אם הטבלה המדוברת גדולה במיוחד (נניח: מכילה BLOBs), חשוב לשים לב שיש לנו מספיק שטח פנוי בדיסק עבור הפעולה.

בזמנו היו הצעות לבצע Drop Index לפני ה OPTIMIZE TABLE ואז Create Index לאחריו, בכדי להמהיר את זמני הביצוע. יש כאן סיכון ברור לרגרסיה משמעותית בביצועים בזמן ה Optimize, ואני לא יודע לומר עד כמה העצה הזו רלוונטית גם היום.

למרות שביצוע Defrag נשמע דבר נחמד מאוד וחיובי, במקרים רבים אין טעם לבצע את הפעולה הזו.
  • טבלה שבה יש הרבה Inserts אבל ה Primary Key שלה תלוי בהוספה (למשל: מפתח ראשי מסוג Auto-increment) - לא צפויה ליהנות הרבה מ OPTIMIZE TABLE.
  • טבלה שבה יש עדכונים אינטנסיביים (נניח: מאות updates בשנייה) עשויה ליהנות מ OPTIMIZE TABLE - אבל חשוב מאוד לתכנן את הפעולה בזמן של מינימום עבודה על הטבלה. הסכנה ל downtime היא ממשית.
אתם יכולים לבדוק את הצורך ב OPTIMIZE TABLE בעזרת כמה שאילתות:

SELECT * 
FROM   sys.schema_index_statistics 
WHERE  table_name = 'tbl_name';

לבדוק כמה הכנסות / מחיקות / עדכונים היו בטבלה. הטבלה הזו מבוססת ככל הנראה על ה performance_schema.

השאילתה הבאה היא שאילתת מערכת:

SELECT table_name, 
       index_length, 
       data_length, 
       data_free, 
       data_length + index_length                           AS total_ו, 
       ( data_free * 100 ) / ( data_length + index_length ) AS free_ratio 
FROM   information_schema.tables 
WHERE  table_schema = 'schema_name'; 

המציגה את ה free_ratio - היחס בין השטח שמוקצה ואינו בשימוש - לשטח שבשימוש.
זכרו ש InnoDB מכוון ל 7%. הכל תלוי במקרה, אבל אחוזים גבוהים (נאמר 40-50% ומעלה) הם מועמדים ל OPTIMIZE.


BLOBs


בכל הדיון עכשיו, לא דיברנו על שדות BLOB/CLOB.
ב InnoDB, עמודות באורך משתנה (כמו BLOB, JSON, TEXT, Varchar, וכו׳) עשויות להישמר בתוך ה Pages של ה Primary Index או עשויות להישמר בקובץ אחר נלווה.

השיקול נעשה ברמת הרשומה והוא עובד כך:
  • אם גודל השדה הוא 40 בתים או פחות (משתנה בשם BTR_EXTERN_FIELD_REF_SIZE, כפול 2) - אזי השדה יאוחסן בתוך המפתח הראשי.
    • בתצורות שונות (למשל: Table's ROW_FORMAT = COMPACT) המספר הזה עשוי לעלות ל 768 או 1024 בתים.
  • מעבר לכך InnoDB ישתדל להשאיר את השדות באורך משתנה בתוך האינדקס הראשי. אם הוא אינו מצליח להכניס ב Page (להזכיר: ברירת המחדל היא 16KB) לפחות 2 רשומות (rows) - אזי הוא יתחיל להוציא את השדות באורך משתנה מתוך ה Primary Index.
    • הוא יתחיל להוציא את השדות מהגדול - לקטן.

בקיצור: שדות בגודל המתקרב ל 8KB, בתצורת ברירת-המחדל של MySQL - הם המועמדים העיקריים לצאת מתוך ה Primary Index לקובץ נפרד.

לקחים:
  • מכיוון שהשימוש בקובץ BLOB חיצוני איננו אחיד לאורך הרשומות בטבלה, הרעיון לבצע SELECT ללא עמודות מסוימות ולהימנע כך מהמחיר של שדות מסוג BLOB על הטבלה - אינו ממש נכון.
  • תאורטית, ב InnoDB אין ממש הבדל בין (Varchar(65,536 ל Text. 
    • בפועל, למרות ש InnoDB תומך בערכי Varchar גדולים, MySQL עצמו לא יאפשר להגדיר סכמה בה סך גודל השדות ברשומה (row) גדול מ 65K - ולכן לא ניתן יהיה להגדיר (varchar(65K
  • כדאי להיזהר מביצוע OPTIMIZE TABLE של טבלה המכילה BLOBs גדולים (למשל: קבצי PDF). חלק מהמדדים (כמו free_ratio) עשויים להצביע על צורך ב defrag לטבלה - בעוד שבפועל ה Primary Index (מה שחשוב) הוא במצב מצוין. השאילתה שבודקת מספר עדכונים ומחיקות - מתאימה יותר למשימה במקרים של BLOBs. 


שיהיה בהצלחה!


------

[א] פעם, פעולת Analyze Table הייתה נועלת את הטבלה - בעיה ממשית. ההתנהגות הזו תוקנה בגרסה 5.6.38 - אך עדיין ניתן למצוא אזהרות באינטרנט לא לבצע פעולת Analyze Table בצורה תכופה. #לא_אקטואלי.

-----

לינקים רלוונטיים:


2018-07-19

כיצד לצמצם פוליטיקה ארגונית? (מבלי לעבוד לבד)


״פוליטיקה ארגונית״ היא אחד הנושאים המושמצים והשנואים ביותר בסביבת העבודה.

מעולם לא פגשתי אדם שדיבר בזכותה, למרות שפגשתי רבים שלקחו בה חלק.

לכאורה נראה שזו רעה חולה, בלתי נמנעת ובלתי-ניתנת לריפוי. תופעת לוואי שלילית, ובעיקר של ארגונים גדולים.

״בכל מקום בו יש יותר מחמישה עובדים - יש פוליטיקה ארגונית״ נאמר לי בתחילת הקריירה. האמירה הזו זכורה לי היטב עד היום.

אני רוצה להציע כמה תובנות. אנסה להראות שניתן להשפיע על פוליטיקה ארגונית, ובעזרת התנהגויות מסוימות, לצמצם או להרחיב אותה.







האם ניתן להימנע מפוליטיקה ארגונית?


פוליטיקה היא עניין מאוד סובייקטיבי - עניין של תפיסת המציאות.

אדם א׳ יכול לחשוד באדם ב׳ שהוא ״נגוע בפוליטיקה״ בעוד אדם ב׳ בכלל מרגיש בעצמו קורבן של פוליטיקה וחף מכל נגיעה בה.

בואו שנייה ננסה לבאר, לצורך הפוסט (ולא בצורה רחבה וכללית יותר) על מה אנחנו מדברים כ״פוליטיקה״.

לצורך הפוסט אתמקד במקרה הפשוט בו יש העדפה על בסיס אישי (חברות / חיבה / יוקרה / ריצוי) ולא על בסיס ״מקצועי״. ההעדפה הזו יכולה להגיע בצורת קידום בתפקיד, קידום רעיונות, קבלת החלטות וכו׳.

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

זו התנהגות אנושית בעיקרה: כאשר אנחנו מחבבים / רוצים לזכות שתשומת לב / מאוימים / כעוסים על אדם אחר - הרגשות עשויות להשפיע על ההחלטות שנקבל בנושאים הקשורים לאותו האדם. הנה דוגמאות:
  1. מנהל הממנה למשימה יוקרתית עובד שהוא מחבב בצורה חברית או מרחיק ממנה עובד אחר שהוא לא-מחבב - מבצע אקט פוליטי ברור. הרגשות משפיעים על שיקול הדעת.
  2. בצורה יותר עדינה, ייתכן מצב של מנהל שממנה עובד למשימה כפיצוי על משימה שוחקת קודמת או כי מועמד אחר למשימה - אכזב את המנהל לאחרונה והוא רוצה "להעביר לו מסר".
  3. בצורה אפילו יותר קלושה - מנהל הממנה למשימה אדם מכיוון שאותו אדם בקשר חברי טוב עם אנשים אחרים הקשורים למשימה (מה שיכול לתרום).
בשלושת המקרים המנהל הפעיל שיקולים לא מקצועיים-ישירים בבחירת האדם שיתמנה למשימה.
על אף שהדוגמה האחרונה נחשבת לרוב כ"לגיטימית" - בחוסר הבנה מסוים היא עשויה להיתפס כאקט פוליטי.

קשה מאוד לדעת בדיוק מה היו מניעיו של המנהל: דמיינו שבכל אחד משלושת המקרים הנ"ל, המנהל מובא בפני הרכב מורחב של בית-המשפט העליון - בכדי להגיע לחקר האמת. האם היה ניתן לקבוע בוודאות את מידת תום הלב? - אני בספק.

לפוליטיקה הארגונית יש שתי תכונות חשובות:
  • פוליטיקה ארגונית היא עניין של תפיסה סובייקטיבית, ולרוב משפיע יותר מה אנשים חושבים ומרגישים, ממה שקרה בפועל.
  • פוליטיקה ארגונית היא עניין ויראלי. אם הרגשתי שעובד אחר זכה ב״הטובות״ בשל התנהגות מסוימת אזי:
    • סביר שאשתף בזה חברים לעבודה - וכך אשפיע גם על התפיסה הסובייקטיבית שלהם לנושא.
    • תגדל הסבירות שיתפתחו אצלי רגשות שליליים לגבי אותו אדם - מה שישפיע על החלטות שלי הנוגעים אליו (וכך אני אבצע ״אקטים פוליטיים״)
    • תגדל הסבירות שאנסה לחקות את התנהגותו, במקרים כאלו ואחרים - על מנת לקבל יחס ״מועדףֿ״ דומה.

למנהל יש מגוון שיקולים לכל החלטה, והחלטות שמתקבלות במידה טובה של תום לב - עשויות להתפרש כצעדים פוליטיים. פעמים רבות חוסר ניסיון או מידע חסר - מובילים להחלטה שנתפסת כפוליטית.

התגובה של האנשים גם היא בלתי-נשלטת. כאשר אנשים חווים בתפיסתם חוויות שליליות שנתפסות כקשורות לפוליטיקה ארגונית - אותם אנשים נוטים להשפיע ולהעצים את הפוליטיקה הארגונית, גם כאשר הם שונאים פוליטיקה ארגונית בכל ליבם.


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






כיצד לתמרץ פוליטיקה ארגונית


לפני שאנו ניגשים לצמצם פוליטיקה ארגונית, שווה להקדיש כמה דקות לראות כמה דוגמאות מוצלחות כיצד ניתן לתמרץ פוליטיקה ארגונית.


תפקיד המנהל הוא התפקיד הטוב ביותר לקדם ממנו פוליטיקה ארגונית, וככל שהמנהל בכיר יותר - כך טוב יותר!
אספק כמה דוגמאות שנתקלתי בהן באופן אישי. אני יכול רק להניח - שהן טיפוסיות.


אין משמרות

פעם היה בחור בצוות שהיה קצת יותר דעתן משאר חברי הצוות האחרים. מסיבות כאלו ואחרות - לא הסתדר לו לעשות משמרות Production כמו כולם. זו הייתה תקופה קשה למדי - ולכולם היה קשה, ללא ספק.

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

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

הנזק מהחלטה נקודתית ותמימה לכאורה - היה עמוק ומתמשך.

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

לכאורה, זו תגובה "פרגמטית" ללא כוונות זדון / ללא רצון להעדפת עובד כזה או אחר. "זה נכפה עלינו".

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



The Chosen

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

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

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

לפעמים, כמנהלים, אפשר להקל ראש במקרים האלו: "הוא עובד טוב, ולכן הגיוני שהוא יקבל עדיפות". "שאר חברי הצוות יתרגלו". הם סוג של תשובות שעשויות לספק את המנהל - אך לא את חברי הצוות. חברי הצוות יכולים לבחוש בלי סוף, להתמרמר, ולפתח תאוריות קונספירציה - מבלי לומר דבר למנהל. הדלק - נשפך לתור המדורה הפוליטית, גם ללא כל כוונה או הבנה מצד המנהל שזה מה שמתרחש.

ראיתי מקרים, של מנהלים חדשים בארגון שביקשו/דרשו להביא עובדים קיימים שלהם - ללא ראינוות קבלה. לרוע המזל - בכמה מקרים הבקשות ההלו נענו בחיוב.

מתוך המקרים הללו, היום גם מקרים בהם האישור ניתן לפני שהמנהל החדש נקלט בכלל בחברה. לפני שהוא יכול היה להבין את אופי החברה, התפקיד המדובר, או פגש בצוות שאותו הוא עומד לנהל.

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







הפרד ומשול

היה לי מנהל, שחלק מכובד מאוד מפגישות ה 1:1 שלנו - היו מוקדש דווקא למקבילים לי, או בכפופים אחרים. למה? - ככה הוא הוביל את הפגישות.
"אז איך ההוא - טוב?"
"איך הצוות שלו? אנשים סומכים עליו? או לא סופרים אותו?"
"הוא נראה לי מה-זה גרוע. נראה לך שהוא מתאים בכלל לתפקיד? הוא צריך בכלל להיות פה, לדעתך?"
"ההוא עשה פאדיחה של הלייף שבוע שעבר, האא? איך כזה דבר קורה - תגיד לי?"

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

בהתחלה נפלתי בפח. חשבתי שאני איש סודו של המנהל והוא באמת מתייעץ איתי על עניינים רגישים והרי-גורל. די מהר למדתי, מהמקבילים לי - שגם איתם הוא מנהל את השיחות הללו, באותו האופן.
מיד הפכתי בשיחות הללו ל"בונקר", והתחלתי לשתוק או להגן על אנשים. קצת מאוחר ממה שהייתי רוצה שיהיה.

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

היחסים בין חברי-הצוות התחלקו בצורה מאוד ברורה: יחסים טובים ובריאים (העובדים שהצטרפו לפני בואו של המנהל, והצליחו לבנות יחסי אמון טובים), ויחסי "כבדהו וחשדהו" - עם העובדים שהצטרפו לאחר בואו של המנהל, ולא חוו אווירה אחרת.


לקדם את טוראי ראיין

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

מה אנחנו עושים כמנהלים, ובני-אדם טובים? מסתכלים בצורה אופטימית על הדברים, ומנסים לעזור.
אולי העובד הזה, שבאמת לא הבריק עד רגע זה - יכול מרגע זה להתבלט אם רק ירצה? אולי זה רק עניין של לתת לו "דחיפה" או "הזדמנות"?

מנהלים גדולים לרוב לא נולדים כך, אבל מה אכפת לנו לנסות?
אתם יודעים כמה לא-נעים, ולא חברי זה לומר לעובד ביובש ש"עד היום לא הראה סימנים שהוא מתאים לתפקיד ניהולי"?

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

נשמע win-win, לא?

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

אומרים שהדרך לגיהנום רצופה בכוונות טובה. הנה דרך אחת טובה שכזו.


דוגמאות קצרות אחרות ששווה להזכיר:
  • מנהל שלא מספק קרדיט לעובדיו. שמו מתנוסס על פרויקטים, עליהם בקושי השפיע. 
    • גם ההתנהגות (הרעה) הזו - היא מודל לחיקוי.
  • מנהל שלא "מרגיע" עובדים סופר-תחרותיים, המתייחסים לעובדים אחרים בזילזול, או עובדים מרירים שלא מפסיקים להתלונן ולעקוץ עובדים אחרים.
    • לפעמים העובד ה"קשה" יהיה ביצועיסט מצויין - ויהיה קשה לאמוד את מידת הנזק הסביבתי שההתנהגות שלו, ובעיקר: קבלת ההתנהגות שלו - גורמת.
    • התפרסמו בשנים האחרונות תוצאות מחקרים שהראו שקיום יריבות מתמשכת בצוות, ותחושת אי-ביטחון לעשות שגיאות ולטעות - היא מנבא מוצלח למדי לאי-הצלחה של ארגון.
      למשל: Google on Psychological Safety
  • וריאציה נוספת: קליקות מזיקות
    חברויות עמוקות בצוות הן דבר מבורך, אך לעתים נוצרות "קליקות" שלא מקבלות אחרים: חבורת מפתחים ותיקים שעבדו ביחד בחברה קודמת, מפתחים מול QA, צוות דוברי רוסית שמשתמשים ברוסית גם מול חבר צוות שאינו יודע את השפה, וכו'.
    • ברגע שהקליקה מאיימת ודוחה עובדים אחרים - אותם עובדים יחפשו קליקה אחרת להאחז בה, מה שעלול להוביל ליריבויות קבוצתיות בחברה. בהחלט לא דבר מועיל.





ואם רוצים בכל זאת, דווקא - לצמצם את הפוליטיקה?


שורשי צמיחת הפוליטיקה בארגון, אם כן, הם ב:
  • תפיסה סובייקטיבית (מדוייקת יותר או פחות) לגבי חוסר-הוגנות.
  • ויראליות של תפיסת חוסר-ההוגנות, המתרחבת גם ע"י תגובת-נגד (נכונה יותר או פחות) לחוסר-ההוגנות שנתפסה. 
אמ;לק: מספיק שתספקו הוגנות ושקיפות גבוהות - והנה צמצמתם את הפוליטיקה הארגונית.

עניין ההוגנות היא עניין בסיסי ביותר. זה אפילו לא עניין אנושי, אלא עניין של בעלי חיים נבונים.

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

עכשיו אם הקופים, והכלבים מזדעקים לנוכח חוסר-הוגנות, מה יהיה על בני-אדם?
בני-אדם מתורבתים יותר? - זה אומר אולי שהם יבלעו את הכעס באותו הרגע, ולא יקפצו על שולחנות ויצרחו - אך הפגיעה עדיין נותרה בעיניה.



זה נשמע נהדר, אבל זה לא כ"כ קל - להשלטת הוגנות ושקיפות יש מחירים.

קשה מאוד לייצר הוגנות שכולם יסכימו לה. בעצם - זה כנראה בלתי אפשרי.
אפשר בהחלט להשתדל ולייצר הוגנות רבה, ע"י סט של כללים ברורים הידועים לכולם - ושכולם ידעו לעקוב אחריהם. גם בתקופה מתקדמת (חחח) כמו המאה ה-21, עדיין חברות אישית היא מנוע עוצמתי בארגונים. אימוץ הוגנות גבוהה עשויה, למשל, לפגוע בקרובים לכם ביותר: "כיצד אני, חבר או עובד מצטיין - לא מקבל יחס מועדף? מה אני - כלב?"

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


לשמור על הוגנות גבוהה זה לא קל. הנה דוגמאות - ומחירים:
  • עובד מצוין מקבל הצעת שכר עדיפה ממקום עבודה אחר? תשמרו אותו (ותפגעו במדרג הוגן של המשכורות) - או תתנו לו ללכת עם כל הכאב שבענין?
    • כאב נלווה: עובדים אחרים שהמוראל שלהם יפגע מעזיבת העובד המצוין ("פעם היו פה עובדים....")
    • ייתכן ותפספסו Deliveries. יש עובדים שעזיבתם עושה זאת. לפספס Deliveries - זה אף פעם לא נעים.
  • עובד "לוחץ" לקבל קידום? האם שתקפו לו את המצב "דוגרי", או תסתובבו סביב?
    • הרבה יותר קל "לנסות", "לתת לו מפת-דרכים להגיע לתפקיד בעתיד" (אצל המנהל הבא), או לשתף פעולה עם השאיפות שלו שאינן בהכרח מציאותיות.
    • "לנסות" את התפקיד הוא בהחלט לא מודל רע - הוא דווקא עשוי להיות טוב מאוד. הבעיה היא כאשר אתם גוררים ודוחים את הפידבק השלילי כאשר זה לא מצליח (מירב הפעמים). "למה לאכזב בחור טוב שרוצה כ"כ?", "ככה להרוס חלום במו ידי?"
  • שינויים ארגוניים, והעלות שכר הם תמיד "חומר נפץ" פוליטי.
    • האם תצליחו לעשות את התהליכים בצורה הוגנת ושקופה, או תעדיפו ל"סיים מהר" את התקופה הלחוצה? (שאיפה ברורה ומובנת).
    • כמה "ג'ובים" תחלקו כדי להרגיע עובדים ממורמרים, שסה"כ אתם רוצים שישארו, בלי להקשות עליכם יותר מידי? כשיש הרבה טרדות על הראש (תמיד יש) - קידום עובד מתוסכל ל"תפקיד לא מזיק" יכול לספק לכם רגע של נחת.
      • הנזק כאן יקרה - אם בעיני עובדים אחרים זה קידום לא מוצדק. "מה הוא עשה בכדי לקבל את התפקיד הנוח הזה? אני רוצה גם!".
    • מנהלים שאפתניים שחותרים בלי סוף להרחבת תחומי-אחריותם הוא גם נושא קשה. הם ינסו "לתפוס" אתכם בהבטחות - שאח"כ תצטרכו לקיים, גם אם זה לא משרת בצורה מיטבית את צרכי הארגון.
  • עובדים הבאים בביקורת זה כלפי זה. אין לכם כוח וזמן - אבל דורשים מכם לעסוק בנושא.
    • כמה קל להגיע למסקנה חפוזה - ו"להוריד את הקוף מהגב".
    • קשה להבין את הנושאים לעומק (אולי לא כדאי), וגם קשה להבין אם יש מולכם עובד גרוע, או עובד תוקפן.
      אני מניח שאת המינימום הזה (מקרי הקיצון) עליכם להבין - ואחרת דרשו מהם להתאמץ, ולהסתדר בכוחות עצמם.

קל לבוא בביקורת "איזה ארגון פוליטי", קשה הרבה יותר לעצור את הפוליטיקה ולשמר אותה ברמה נמוכה.

כמו בחוקי הפיסיקה, גם כאן יש נוסחה פשוטה שמתאר טוב את המצב הרצוי:

Self < Team < Organization


אם תצליחו לשמר את הכלל הזה לאורך זמן - מובטח לכם ארגון דל בפוליטיקה.

מדי פעם אנשים חלקלקים יוכלו "לשחק אותה", כאילו באמת הם מחויבים לארגון. חשוב לבחון מעשים - ולא דיבורים.

בכדי לבנות ולשמר ארגון דל-בפוליטיקה, חשוב מאוד לגייס ולשמר עובדים שמוכנים ויודעים לשים את טובת הארגון לפני טובתם שלהם, מתוך הנחה שאם יעשו כן - הדבר יטיב גם עמם.

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

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



סיכום


עד כמה שאני יכול לומר, פוליטיקה ארגונית לא נגרמת בעיקר ע״י מנהלים פוליטיים ונכלוליים. היא נגרמת בעיקר ע״י מנהלים לא מנוסים או כאלה שלא מבינים עד הסוף את הדינמיקה הארגונית העמוקה. מקסימום: מנהלים חסרי סבלנות שלא מצליחים לחשוב ולקחת החלטות ארוכות טווח.
הפוליטיקה הארגונית מושפעת בעיקר מההנהלה הבכירה - שלא עושה דיי להגביל אותה, כי יש נושאים בוערים אחרים. ההחלטות הנדרשות על מנת לצמצם את ה״פוליטיקה הרעה״ בארגון הן בהחלט לא קלות, ומלווות במחיר ממנו נרצה להימנע.

זהו חוב ניהולי, ממש כמו technical debt: כאשר עשינו ״קיצור לא אלגנטי״ וזכינו להצלחה מקומית - זו עשויה להראות כמו פשרה טובה דיה. לאורך זמן, החוב מצטבר ותופח ובלי לשים לב - אנחנו יכולים למצוא את עצמנו עם ״גיבנת” ממנה קשה מאוד להשתחרר.

כמו בסיפור של עוץ לי גוץ לי - החוב סופו להיפרע.  [1]

שיהיה בהצלחה!



----

[1] הטוחן וביתו קיוו בכל מאודם שעוץ-לי לא יבוא לגבות את החוב - אבל הוא בא.
נכון, בסוף הסיפור הוא מספק הזדמנות שניה, ולאחר שבת הטוחן מגלה את שמו הוא רוקע ברגלו - ונעלם.
אבל היי - זו אגדה. חוב ניהולי לא נעלם ככה במציאות.



2018-07-08

נושאים מתקדמים ב MySQL: חלק ב' - Generated columns ו json_each

פוסטים בסדרה:
"תסביר לי" - גרסת ה SQL
לקחת את הביצועים ברצינות, בעזרת MySQL Performance Schema
נושאים מתקדמים ב MySQL: חלק א' - עבודה עם JSON
נושאים מתקדמים ב MySQL: חלק ב' - json_each  ו Generated Columns



בפוסט הקודם דיברנו על שימוש ב json column ב MySQL.

אחת המגבלות הגדולות של שדות json הם מהירות החיפוש בהם:
  • אם השדות שמורים כ json - יש לקרוא את ה json כולו מהדיסק לזיכרון - על מנת לשלוף ערך בודד.
  • אם השדות שמורים כ text column - אזי יש עוד עבודת CPU של פענוח מבנה ה json - זמן לא זניח ב jsons גדולים. להזכיר: בעמודה מטיפוס json הם שמורים כ key-value עם גישה מהירה יותר לפי מפתח.
הפתרון האפליקטיבי המתבקש שבו מפתחים שונים נוקטים הוא לאחסן "עותק" של מספר מצומצם של שדות עליהם יש חיפוש - ב columns מקבילים לזה של ה json. ההשפעה על הביצועים - תהיה לרוב דרמטית.

הנה סכמה כזו לדוגמה:

[ Id (guid/auto-inc), json_data (json), field_x (varchar(100)), field_y (int) ]

את השדות x ו y - נרצה לרוב להחזיק גם בתוך ה josn וגם בצד לצורך גישה מהירה / שימוש באינדקסים.
למה שכפול נתונים?
אם נאחסן אותם רק מחוץ ל json - העבודה עם אובייקט ה json תהיה מסורבלת יותר.
לפעמים השדה שאנו מוציאים הוא גם ערך וגם מיקום באובייקט - למשל: המחיר של ההזמנה המאוחרת ביותר (כאשר יש לנו מערך של גרסאות של הזמנות).

את התהליך הנ"ל - ניתן לבצע בצורה פשוטה יותר בעזרת יכולת של MySQL שנקראת generated column, או בעברית "עמודות מחוללות" (מלשון לחולל, ולא מלשון חילול).

נתחיל בדוגמה, איך מגדירים עמודה שכזו:

ALTER TABLE policies
  ADD COLUMN state VARCHAR(30) GENERATED ALWAYS
    AS (json_unquote(json_extract(`json_data`,'$.address.state'))) STORED
;

אני מוסיף שערכו יהיה הביטוי (expression) שבסוגריים לאחר המילה השמורה AS.
  • חשוב לי להשתמש ב json_unquote על מנת שהעמודה לא תכיל מירכאות - וכך אוכל לבצע חיפוש יעיל מול האינדקס.
  • הביטוי GENERATED ALWAYS הוא רשות - ועוזר להבליט את העובדה שמדובר ב generated column - עבור קוראים עתידיים.


תזכורת חשובה: אם השאילתה עושה פעולה כלשהי על שדה מאונדקס (נאמר: state) - לא יהיה שימוש באינדקס. למשל:

... 
WHERE my_state = json_unquote(state)

לא טוב!

השינוי הבא יאפשר שימוש באינדקס (כי את הערך "כפי שהוא" ניתן להשוות בינרית לאינדקס):

... 
WHERE json_quote(my_state) = state

אבל הכי טוב זה להכין את האינדקס מראש בצורה הכי "טבעית" לשאילתה שאפשר - וכך לחסוך טעויות מרגיזות.


בגדול יש שני סוגים של generated columns:
  • Virtual - המחושב בזמן הגישה לרשומה. 
    • בסיס הנתונים ישמור את הביטוי החישובי (expression) כ metadata על הסכמה, ובכל גישה לרשומה / הפעלת trigger - יתבצע החישוב מחדש. קונספט דומה מאוד ל "calculated field" במערכות BI.
    • אנו חוסכים מקום בדיסק - אבל מכבידים על ה CPU בכל גישה. 
    • זהו ערך ברירת המחדל - אם לא ציינתם כלום.
  • Stored - הערכת הביטוי תבוצע בזמן יצירה / עדכון של הרשומה, וישמר לדיסק כמו עמודות אחרות.
    • יש מחיר בנפח אכסון, אך לרוב - הרבה פחות עבודת CPU.
    • זו הגישה הטבעית בעבודה עם json.


לצורך העניין generated column יכול לשמש לצרכים נוספים מלבד json.

למשל:

CREATE TABLE periods 
  id VARCHAR(32), 
  start_date_millis INT, # WARN: https://en.wikipedia.org/wiki/Year_2038_problem  
  end_date_millis INT,   # WARN: https://en.wikipedia.org/wiki/Year_2038_problem  
  start_date_sec AS (state_date_millis * 1000) VIRTUAL 
;

יש כמה מגבלות על שימוש ב generated columns שכדאי להכיר. בביטוי של עמודה מחוללת לא ניתן להשתמש ב:
  • פונקציות לא דטרמיניסטיות - כאלו שיציגו ערכים שונים אם הופעלו פעם שניה. למשל: ()now או ()current_user.
  • sub-queries
  • Parameters / Variables או User Defined Functions.
  • עמודת AUTO_GENERATED או תכונת AUTO_INCREMENT.
  • שדה generated יכול להיות מבוסס רק על שדות generated המופיעים לפניו.
  • לא ניתן להשתמש על ה stored generated column באילוצים של FK מסוגי ON_UPDATE / ON_DELETE

עוד פרט מעניין: ניתן להשתמש באינדקס (משני) גם על virtual generated column מעל innoDB. הוא יהיה מסוג BTree בלבד (כלומר: לא FULLTEXT או GIS).

בהוספת האינדקס ייתכן שתצטרכו להוסיף הוראת WITH VALIDATION - על מנת לא לקבל שגיאה שאורך השדה המחושב שהגדרתם הוא קטן מדי.



קצת יותר על השימוש בפונקציות ה JSON של MySQL



התבקשתי ע״י קורא לפרט קצת יותר איך עם פונקציות ה json של MySQLשהזכרתי בפוסט הקודם. אתן דוגמה מבוססת על מקרה שהשתמשתי בו לאחרונה.


רקע: אני רוצה לבצע מיגרציה לשם של אלמנט בתוך מבנה json שאנו עושים בו שימוש. שם האלמנט היום הוא breakdown אבל השם לא כ"כ נכון - ואני רוצה לשנות אותו ל amounts. הנה דוגמה פשוטה למבנה ה json הקיים לפני השינוי:

[{"policyId":"policy_id","lob":"GL","breakdown":{"PREMIUM":{"amount":423.17}}}]  

וכך אני רוצה שמבנה יראה לאחר השינוי:

[{"policyId":"policy_id","lob":"GL","amounts":{"PREMIUM":{"amount":423.17}}}] 


איך לעזאזל ניגשים למיגרציה "עמוקה" בתוך שדה json? האם זה חייב להיות פרויקט מורכב?

ברור שלא.

אז הנה אני אעשה את השינוי בכמה שלבים פשוטים:

ראשית, אני רוצה ליצור שדות כפולים: גם breakdown וגם amounts - כי באופן טבעי בזמן deployment ירוצו 2 גרסאות של הקוד (גרסה חדשה שמחפשת אחר amounts וגרסה ישנה שמחפשת אחר breakdown). אני צריך להתכונן לכפילות הזו.
אני דואג לכך שבזמן ה deploy (כמה דקות) - לא יווצרו רשומות חדשות (במקרים אחרים ניתן למצוא אותן בעזרת זמן יצירה, ולתקן ידנית אח״כ). 

בניית שאילתה של SQL היא עניין של ניסוי וטעיה, והדבר הזה נכון גם בעבודה עם  json.

אני מתחיל בשאילתה פשוטה של SELECT על מנת לראות אם קלעתי נכון ב query.
בחרתי לי רשומה שהמפתח שלה הוא '009277a371b8c3def40996a754085030' על מנת לבצע את הניסויים ב scale קטן.

SELECT Json_insert(financial_attribution, '$[0].foo', 2) 
FROM   `payments` 
WHERE  id = '009277a371b8c3def40996a754085030'; 

במבט ראשון זה קצת מבלבל שאני משתמש ב ()JSON_INSERT בפקודת SELECT.
מה קורה כאן?

אני שולף שדה json בשם `financial_attribution` ואז מבצע עליו מניפולציה בזיכרון. המניפולציה שבחרתי היא הכנסה של ערך. מפתח מוזר בשם foo עם ערך של 2. רק לצורך הבדיקה. 

הנה התוצאה:

[{"foo": 2, "lob": "GL", "policyId": "policy_id", "breakdown": {"PREMIUM": {"amount": 423.17}}}]   

השדה נוסף בהצלחה (לי לקח ניסיון או שניים עד שזה עבד) - אך שום מידע לא השתנה בבסיס הנתונים.

עכשיו ננסה משהו יותר אמיתי:

SELECT Json_insert(financial_attribution, '$[0].amounts'                   financial_attribution -> '$[0].breakdown') 
FROM   `payments` 
WHERE  id = '009277a371b8c3def40996a754085030'; 


לקחתי מתוך ה json את אובייקט ה breakdown - והכנסתי אותו בחזרה כ amounts:

[{"lob": "GL", "amounts": {"PREMIUM": {"amount": 423.17}}, "policyId": "policy_id", "breakdown": {"PREMIUM": {"amount": 423.17}}}] 


אחרי שאני רואה שזה מצליח אני יכול להסיר את תנאי ה where ולראות את התוצאה על מגוון ערכים. מוודא שהכל תקין.

אנסה בצד דוגמה שגם שאילתת ה update תקינה:

UPDATE `payments` 
SET    `financial_attribution` = Json_insert(`financial_attribution`, '$[0].amounts', 
                                 financial_attribution -> '$[0].breakdown') 
; 

ואז הוסיף אותה ל db migrations של השינוי.

אחרי שכל המיגרציה הסתיימה (בהצלחה), אני יכול למחוק את אובייקט ה breakdown מתוך ה json בעזרת פקודת  ()JSON_REMOVE. 
אפשרות אחרת היא פשוט להשאיר את הנתונים העודפים - אם הם לא מפריעים לנו.

עוד דרך מהירה (ולעתים יותר יעילה) לבצע תיקונים ב jsons היא בעזרת ()REPLACE פשוט על טקסט - אבל חשוב מאוד להיזהר מהחלפות לא-צפויות.



השלמות ליכולות ה json של MySQL 5.7


אם אתם זוכרים את הפוסט הקודם - הבטחתי לכם workarounds למחסור של MySQL 5.7 ב-2 פונקציות שימושיות בעבודה עם json.

מהדרך בה אני משתמש ניתן לגזור, בקלות יחסית, וריאציות נוספות - לשימושכם.



קריאת הערך האחרון במערך שבתוך json


אני רוצה לקבל את השדה האחרון במערך שבתוך json:

SELECT JSON_EXTRACT(`from`,CONCAT("$[",JSON_LENGTH(`from`)-1,"]")) FROM `table`;


זה קצת תרגיל: אני מוצא את אורך המערך (במקרה שלנו, תחת תכונה בשם from) ואז מרכיב שאילתה בעזרת ()CONCAT - ומריץ אתה. הנה קלט לדוגמה שעליו תעבוד השאילתה:

{ from: ["a","b","c"], "to": [ "d", "e" ] }

התוצאה תהיה ״c״.

הנה דוגמה ב DB Fiddle שאפשר קצת ״לשחק״ איתה:


שימו לב ל-2 מקרי-קצה בתחתית הדוגמה.
לי זה מעולם לא הפריע, כי עבדתי עם מבנים ידועים שהם מערך - אבל אפשר גם לבנות פונקציה מורכבת יותר, ולהתגונן בפני המקרים הללו (או פשוט לשדרג ל MySQL 8).




גרסה מאולתרת ל json_each - טיפול בכל איבר במערך שבתוך json


על בסיס הדרך בה קראנו את האיבר האחרון, אנחנו יכולים לטייל על כל המערך. עם מעט עזרה נוספת.

הנה דוגמה לשאילתה כזו:

select  n, 
        JSON_EXTRACT(`data`, concat('$.items[', n - 1, '].price')) as price 
from `my_table` 
  join numbers on (n <= JSON_LENGTH(`data`->'$.items') and n > 0) 
;

כאשר התוצאה נראית כך:


n הוא מספר האיבר ברשימה (אינדקס על בסיס 1), ו price הוא... המחיר.

והנתונים נראים כך:


עשינו join עם טבלה של מספרים (0-255 לרוב מספיקה) ואז, עבור כל מספר באורך המערך של ה items - ביצענו פעולת שליפה מתוך ה json על המקום הזה, בהתבסס על ״תרגיל״ ה CONCAT שהשתמשנו בו קודם לכן.

הכי-אלגנטי-בעולם? - לא.
עובד ושימושי - כן!

הנה אתם יכולים לשחק ב fiddle שיצרתי לקוד הזה: https://www.db-fiddle.com/f/dmA8af4CHJ3xkx4fzV99Zw/0


בוודאי שמתם לב למבנה קצת לא-שגרתי, שנועד בכדי ליצור את ה View המספק את המספרים. גם כאן יש תרגיל ״מוכר״ בעולם של MySQL:

CREATE OR REPLACE VIEW generator_16 
AS SELECT 0 n UNION ALL SELECT 1  UNION ALL SELECT 2  UNION ALL 
   SELECT 3   UNION ALL SELECT 4  UNION ALL SELECT 5  UNION ALL 
   SELECT 6   UNION ALL SELECT 7  UNION ALL SELECT 8  UNION ALL 
   SELECT 9   UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL 
   SELECT 12  UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL 
   SELECT 15 
; 

CREATE OR REPLACE VIEW numbers
AS SELECT ( hi.n * 16 + lo.n ) AS n
     FROM generator_16 lo, generator_16 hi
;


עזבו לרגע את המבנה העמוס. אם תקראו את הקוד - הוא נראה מאוד הגיוני.
מדוע הסרבול הזה? האם אין דרך יותר פשוטה? - הנה פוסט יפה עם כל הפרטים.

אפשר למצוא את Fiddle ממקוד של ה generator על מנת ״לשחק״ איתו: https://www.db-fiddle.com/f/jCRetSiTaKqz5SUiQQG8Py/0




סיכום


טוב, נראה לי שהכנסו דיי מידע לפוסט יחיד.
מבחינתי הנושאים הללו מכסים יפה את העבודה ב MySQL (״הוסמכת - סרג׳יו״), ומכאן ניתן להמשיך הלאה, לנושאים מתקדמים אחרים ב MySQL.


שיהיה בהצלחה!