Pessimistic Lock & Optimistic Lock

קצת רקע על מסדי נתונים:

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

  • PostgreSQL
  • MySQL
  • Microsoft SQL Server
  • Oracle Database
  • SQLite (קל משקל, נפוץ באפליקציות מובייל)

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

  • MongoDB (מבוסס מסמכים / Document)
  • Redis (מפתח-ערך מהיר במיוחד בזיכרון / In-Memory Cache)
  • Cassandra / ScyllaDB (מבוססי עמודות רחבות לעומסים אדירים)
  • Neo4j (מסד נתונים גרפי לניהול רשתות וקשרים מורכבים)

עכשיו כשיש לנו ידע בסיסי בסוגי מסדי נתונים, אנחנו יכולים לדון בגישות השונות.
במסדי נתונים רלציוניים מסורתיים (כמו PostgreSQL), בדרך כלל עובדים על שרת בודד (או קלאסטר מוגבל) עם דגש חזק על עקביות מוחלטת (Consistency), ולכן קל מאוד להפעיל בהם נעילות פסימיות.
ב-NoSQL, המערכות בנויות לרוב סביב זמינות (Availability) וביזור (Partition Tolerance). לכן:


מנגנוני Locking (נעילות) במסדי נתונים רלציוניים (SQL) הם הדרך שלנו להתמודד עם Race Conditions - מצבים שבהם שני משתמשים או שני תהליכים מנסים לעדכן את אותה שורה במקביל, מה שעלול לגרום לאובדן נתונים (Lost Update).

Pessimistic Lock

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

נפוץ בעיקר במצבים הבאים (אבל לא רק):

איך משלבים אותו בשאילתא? (SQL)
ברוב מסדי הנתונים הרציונלים SELECT ... FOR UPDATE:

BEGIN TRANSACTION;
SELECT * FROM Accounts WHERE id = 123 FOR UPDATE; // lock this data in DB
UPDATE Accounts SET balance = balance - 100 WHERE id = 123; // update the data
COMMIT; // realese the data 

שיקולים, יתרונות וחסרונות:

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

Optimistic Lock

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

איך עושים את זה בפועל? בדרך כלל מוסיפים לטבלה עמודת Version (מספר גרסה רץ) או Timestamp (חותמת זמן).

נפוץ בעיקר במצבים הבאים (אבל לא רק):

איך משלבים אותו בשאילתא? (SQL)

UPDATE Products
SET stock = stock - 1,
    version = version + 1
WHERE id = 456 AND version = 5;

איך יודעים אם הייתה התנגשות? בודקים כמה שורות עודכנו (Rows Affected):

שיקולים, יתרונות וחסרונות:

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

דגשים לשימוש בשתי הגישות:

  • שילוב עם ORM (כמו Hibernate / JPA): ברוב סביבות הפיתוח המודרניות (כמו Java/Hibernate), אין צורך לכתוב את השאילתות ידנית. ב-Hibernate למשל, מספיק להוסיף את האנוטציה @Version מעל שדה הגרסה באובייקט ה-Entity, והפריימוורק כבר ידאג אוטומטית לבדוק את הגרסה בכל פעולת UPDATE.
  • Deadlocks בנעילה פסימית: כשמשתמשים בנעילות פסימיות מרובות (למשל לנעול גם חשבון א' וגם חשבון ב' במקביל), חייבים להקפיד לנעול אותם תמיד באותו סדר (למשל תמיד לפי ID מהקטן לגדול), אחרת תיצרו מצב של Deadlock שבו שתי טרנזקציות מחכות זו לזו לנצח.
  • Retry Logic בנעילה אופטימית: אם בחרתם באופטימית, תצטרכו לתכנן את הקוד שלכם כך שידע לתפוס את השגיאה (למשל StaleObjectStateException) ולבצע ניסיון חוזר (Retry) אוטומטי למשיכת הנתונים העדכניים וביצוע הפעולה מחדש.
🏠 Back to Orly's Code Corner