Optimistic Concurrency vs Locks في SQL Server
🔒⚡ Optimistic Concurrency vs Locks في SQL Server
لما اتنين يعدّلوا نفس الـ Order في نفس الوقت… مين فيهم يكسب؟ 😄
تخيّل معايا إن عندنا متجر إلكتروني اسمه SuperShop 🛒
وعندنا Order بالشكل ده:
Order #1001Status: PendingTotal: 1500 SAR
وفي نفس اللحظة حصل السيناريو ده:
👨💼 أحمد من فريق خدمة العملاء فتح الـ Order.
👩💼 منى من فريق العمليات فتحت نفس الـ Order.
الاتنين شايفين:
Status = Pending
أحمد قال:
العميل أكد الطلب، خليني أعمله Approved ✅
وفي نفس الوقت منى قالت:
العميل طلب إلغاء الطلب، خليني أعمله Cancelled ❌
أحمد ضغط Save الأول.
بقت الداتا:
Status = Approved
بعدها بثانيتين منى ضغطت Save.
بقت الداتا:
Status = Cancelled
وهنا السؤال الخطير 😅:
طب تعديل أحمد راح فين؟ 🤨
اتمسح.
مش بس اتمسح…
ده اتمسح من غير ما أحمد أو منى يعرفوا إن حصل Conflict أصلاً.
وده واحد من أشهر مشاكل الـ Concurrency واسمه:
💥 Lost Update
ومن هنا تبدأ حكايتنا مع:
- SQL Server Locks 🔒
- Pessimistic Concurrency
- Optimistic Concurrency ⚡
rowversion- EF Core Concurrency Tokens
- Blocking
- Deadlocks
- Snapshot Isolation
خلينا نمشي واحدة واحدة.
🧠 أولًا: يعني إيه Concurrency؟
Concurrency ببساطة معناها:
أكتر من عملية بتحاول تتعامل مع نفس الداتا في نفس الوقت.
ممكن يكون عندك:
User AUser BAPI RequestBackground WorkerScheduled Job
وكلهم بيتعاملوا مع نفس الـ Database.
مثلًا:
Order 1001
ممكن يتفتح في نفس اللحظة من أكتر من Server.
خصوصًا لو عندك Architecture زي:
Load Balancer|-------------------------| | |API-1 API-2 API-3\ | /\ | /SQL Server
هنا مفيش ضمان إن Request 1 وRequest 2 مش هيشتغلوا على نفس الـ Row في نفس اللحظة.
وده طبيعي جدًا.
المشكلة مش في وجود Concurrency.
المشكلة في:
إنت هتدير الـ Concurrency إزاي؟
🔒 هنا تدخل SQL Server Locks
SQL Server مش سايب الدنيا سداح مداح 😄
لما Transactions تتعامل مع الداتا، SQL Server بيستخدم Locks عشان يحافظ على سلامة البيانات.
يعني لو Transaction بتعدل Row، مش أي Transaction تانية تقدر تدخل تعدله براحتها في نفس اللحظة.
مثلًا أحمد بيعمل:
UPDATE OrdersSET Status = 'Approved'WHERE Id = 1001;
SQL Server غالبًا هيحتاج يعمل:
Exclusive Lock
على الـ Resource اللي بيتعدل.
يعني ببساطة:
🚫 أنا حاليًا بعدّل هنا، محدش يعدّل معايا في نفس اللحظة.
🔐 أهم أنواع الـ Locks
خلينا نبسطهم.
1️⃣ Shared Lock — S Lock
بيستخدم غالبًا أثناء القراءة.
الفكرة:
أنا بقرأ الداتا بس.
أكتر من Transaction ممكن يكون عندهم Shared Locks في نفس الوقت.
يعني:
Ahmed → ReadMona → ReadSara → Read
تمام.
لكن لو حد عايز يعمل Write، هنا ممكن يحصل تعارض حسب الـ Isolation Level والسيناريو.
2️⃣ Exclusive Lock — X Lock
ده بتاع الكتابة ✍️
لو أحمد بيعدل:
UPDATE OrdersSET Status = 'Approved'WHERE Id = 1001;
SQL Server محتاج Exclusive Lock.
معناه:
محدش تاني يقدر يعمل تعديل متعارض على نفس الـ Resource لحد ما أحمد يخلص.
3️⃣ Update Lock — U Lock
ده Lock وسيط SQL Server ممكن يستخدمه قبل التحويل إلى Exclusive Lock.
الهدف الأساسي منه تقليل احتمالية Deadlocks في بعض سيناريوهات:
Read → Then Update
يعني SQL Server بيقول:
أنا بقرأ، بس غالبًا ناوي أعدل بعد شوية.
🏢 والـ Lock بيتعمل على إيه؟
ممكن SQL Server يقفل على مستويات مختلفة:
RowPageTable
وأحيانًا Key / Range وغيره.
مثلًا:
Row Lock
يعني Row معين.
بينما:
Table Lock
يعني تأثير أوسع بكتير.
وعشان كده Locking Strategy ممكن تأثر جدًا على الـ Performance.
😎 طب كده Lost Update اتحلت؟
مش بالضرورة.
وهنا النقطة المهمة جدًا.
خلينا نرجع لأحمد ومنى.
الساعة 10:00 أحمد قرأ:
Order 1001Status = Pending
وفي نفس اللحظة منى قرأت:
Order 1001Status = Pending
بعدها أحمد قعد 5 دقايق بيتكلم مع العميل ☎️.
ومنى قعدت 3 دقايق بتراجع الطلب.
يعني الـ Transaction الأصلية مش مفتوحة طول الوقت.
التطبيق جاب الداتا من SQL Server وطلعها للمستخدم.
بعد شوية أحمد عمل:
Approved
وبعدين Save.
وبعده منى عملت:
Cancelled
وبعدين Save.
SQL Server وقت الـ UPDATE نفسه عمل Lock.
لكن المشكلة إن SQL Server ما يعرفش إن منى عدلت بناءً على نسخة قديمة من الـ Order.
💡 ودي نقطة محورية جدًا:
⚠️ Locks مش معناها تلقائيًا إنك محمي من كل Lost Updates.
SQL Server بيحمي العمليات اللي بتحصل دلوقتي.
لكن لو الـ User كان شايف نسخة قديمة من البيانات من خمس دقايق، الموضوع مختلف.
وهنا يظهر:
⚡ Optimistic Concurrency
🧠 يعني إيه Optimistic Concurrency؟
الفلسفة بسيطة جدًا.
بدل ما أقول:
أنا خايف إن حد يعدل الـ Order، فهفضل ماسكه Lock طول ما المستخدم فاتح الشاشة.
بنقول:
غالبًا محدش هيعدل نفس الـ Order في نفس الوقت.خلّي الناس تشتغل براحتها، ولما حد يعمل Save نتأكد إن الداتا ما اتغيرتش من وقت ما قراها.
وده ليه اسم:
Optimistic
لأننا متفائلين إن Conflict مش هيحصل كتير 😄.
لكن لما يحصل…
نكتشفه.
🔒 العكس: Pessimistic Concurrency
Pessimistic معناها:
أنا متوقع إن Conflict ممكن يحصل، فهقفل الـ Resource قبل ما أشتغل عليه.
تخيلها كده:
أحمد دخل يعدل Order 1001.
حطينا لافتة:
🔒 Ahmed is editing this Order.
منى تيجي تعدل…
نقول لها:
⏳ استني أحمد يخلص.
ده Pessimistic Locking.
😅 طب ليه ما نعملش كده وخلاص؟
لأن عندك Web Application.
ممكن أحمد يفتح صفحة Edit الساعة:
10:00
ويروح يعمل قهوة ☕.
ويرجع:
10:20
هل معقول تمسك Database Transaction وLock لمدة 20 دقيقة؟
طبعًا لا 😄.
هتعمل كارثة.
ممكن يحصل:
BlockingConnection exhaustionDeadlocksPoor throughputTerrible user experience
عشان كده معظم Web Applications بتحب:
⚡ Optimistic Concurrency
🪄 الحل الجميل: rowversion
SQL Server عنده Data Type ممتاز جدًا اسمه:
rowversion
خلينا نعدل جدول Orders.
CREATE TABLE Orders(Id INT PRIMARY KEY,Status VARCHAR(50),Total DECIMAL(18,2),RowVersion ROWVERSION);
كل Row بقى عنده Version.
مثلًا:
Order 1001Status = PendingTotal = 1500RowVersion = 0x00000000000007D1
أحمد فتح الـ Order.
فأخذ:
Status = PendingVersion = 0x07D1
منى فتحت الـ Order.
برضه أخذت:
Status = PendingVersion = 0x07D1
الاتنين عندهم نفس النسخة.
تمام لحد هنا 👍.
👨💼 أحمد يعمل Save
أحمد غيّر:
Pending↓Approved
بدل ما نعمل:
UPDATE OrdersSET Status = 'Approved'WHERE Id = 1001;
نعمل:
UPDATE OrdersSET Status = 'Approved'WHERE Id = 1001AND RowVersion = @OriginalRowVersion;
يعني:
عدّل الـ Order لو الـ Version لسه هو نفس الـ Version اللي أحمد قرأه.
وكان:
@OriginalRowVersion = 0x07D1
SQL Server يلاقيه زي ما هو.
يعمل Update ✅.
وبمجرد الـ Update، الـ rowversion يتغير تلقائيًا.
بقى مثلًا:
Status = ApprovedVersion = 0x07D2
جميل جدًا.
👩💼 بعدها منى تعمل Save
منى لسه معاها:
Version = 0x07D1
فتبعت:
UPDATE OrdersSET Status = 'Cancelled'WHERE Id = 1001AND RowVersion = 0x07D1;
لكن SQL Server يبص على الداتا الحالية:
Current Version = 0x07D2
مش:
0x07D1
إذًا:
Rows affected = 0
🎉 وهنا اكتشفنا الـ Conflict.
بدل ما تعديل أحمد يضيع في صمت…
التطبيق يعرف إن:
🚨 يا منى، الـ Order اتغير من وقت ما فتحتي الشاشة.
وده هو:
⚡ Optimistic Concurrency Control
🤯 لاحظ الفرق
بدون Optimistic Concurrency:
Ahmed reads PendingMona reads PendingAhmed → ApprovedMona → CancelledFinal Result:Cancelled
تعديل أحمد ضاع 💀.
لكن باستخدام rowversion:
Ahmed reads Version 10Mona reads Version 10Ahmed updates Version 10↓DB becomes Version 11Mona tries Version 10↓Conflict 🚨
ودي الفكرة كلها.
🕐 نقطة مهمة جدًا: rowversion مش Time
الاسم القديم للـ rowversion كان:
timestamp
وده سبب كمية لخبطة تاريخية عظيمة 😂.
لأن ناس كتير افتكرت إنه Date/Time.
لكن:
rowversion ≠ DateTime
مش معناه:
2026-09-03 15:30
هو Binary Value بيتغير مع Updates.
مثال:
0x00000000000000010x00000000000000020x0000000000000003
تقريبًا Conceptually.
فالأصح تستخدم:
rowversion
مش تعتمد عليه كتاريخ.
🚀 طب نعمل ده إزاي في EF Core؟
وهنا الموضوع بيبقى ألطف جدًا.
عندنا Entity:
public class Order{public int Id { get; set; }public string Status { get; set; }public decimal Total { get; set; }[Timestamp]public byte[] RowVersion { get; set; }}
الـ:
[Timestamp]
بتقول لـ EF Core:
الحقل ده Concurrency Token.
🎯 EF Core بيعمل إيه ورا الستار؟
لو أحمد قرأ Order:
Id = 1001Status = PendingRowVersion = ABC
وبعدين غيّره:
order.Status = "Approved";await dbContext.SaveChangesAsync();
EF Core مش هيعمل تقريبًا:
UPDATE OrdersSET Status = 'Approved'WHERE Id = 1001;
لكن هيعمل حاجة Conceptually شبيهة بـ:
UPDATE OrdersSET Status = @StatusWHERE Id = @IdAND RowVersion = @OriginalRowVersion;
🔥 هنا السحر.
لو الـ RowVersion اتغير:
0 Rows affected
EF Core يفهم إن فيه Concurrency Conflict.
فيرمي:
DbUpdateConcurrencyException
🛡️ فنعمل Handle للـ Exception
مثلًا:
try{await dbContext.SaveChangesAsync();}catch (DbUpdateConcurrencyException){throw new Exception("The order was modified by another user.");}
لكن في Production طبعًا هنحتاج Handling أحسن.
🤔 لما يحصل Conflict نعمل إيه؟
دلوقتي منى حاولت تلغي الطلب.
لكن أحمد سبقها وعدله.
عندنا عدة استراتيجيات.
🥇 Database Wins
نقول لمنى:
النسخة اللي عندك قديمة. هجيبلك آخر نسخة من السيرفر.
مثلًا:
Your Version:PendingCurrent Database Version:Approved
وبعدين تعمل Refresh.
ده:
Database Wins
🥈 Client Wins
ممكن في بعض الأنظمة تقول:
لأ، تعديل منى أهم. اعمل Refresh للـ Version وحاول تاني بتعديلها.
يعني عمليًا:
Ahmed → ApprovedMona → Cancelled
وفي النهاية:
Cancelled
بس الفرق إن الموضوع حصل بوعي مش Lost Update بالصدفة.
🥉 Merge
ودي ألطف Strategy في بعض التطبيقات.
مثال:
أحمد غير:
Status
ومنى غيرت:
CustomerNote
ليه نرمي تعديل واحد منهم؟
ممكن نعمل Merge:
Status = ApprovedCustomerNote = Call customer tomorrow
وده مفيد في الأنظمة اللي فيها Forms كبيرة.
🖥️ الـ API ممكن يرجع إيه؟
مثلًا لو Conflict حصل، ممكن الـ API يرجع:
409 Conflict
وممكن الـ Response يكون:
{"message": "The order has been modified by another user.","currentStatus": "Approved"}
والـ Frontend يعرض:
⚠️ This order was modified by another user.Current Status: Approved[Reload][Review Changes]
وده UX محترم جدًا.
🔒 طب فين الـ Locks بقى؟
هنا أهم نقطة في المقال كله.
ناس كتير بتفكر:
طالما استخدمت Optimistic Concurrency يبقى SQL Server مش بيستخدم Locks.
❌ غلط.
Optimistic Concurrency مش معناه إن مفيش Locks.
وقت الـ UPDATE:
UPDATE Orders ...
SQL Server لسه محتاج يحمي عملية الكتابة.
يعني ممكن يعمل Exclusive Locks أثناء تنفيذ الـ Update.
الفرق الحقيقي هو:
Pessimistic
Lock↓Work↓Wait↓Update↓Unlock
بينما Optimistic:
Read↓Work freely↓Try Update↓Check Version↓Success ✅orConflict 🚨
يعني إحنا مش بنلغي الـ Locking Engine بتاع SQL Server.
إحنا بنغير طريقة إدارة التعارض على مستوى التطبيق.
🚧 Blocking يعني إيه؟
نفترض أحمد بدأ Transaction:
BEGIN TRANSACTION;UPDATE OrdersSET Status = 'Approved'WHERE Id = 1001;
وماعملش:
COMMIT;
منى حاولت:
UPDATE OrdersSET Status = 'Cancelled'WHERE Id = 1001;
ممكن تلاقي منى قاعدة مستنية:
Waiting...Waiting...Waiting...
ليه؟
لأن أحمد ماسك Lock.
ده اسمه:
⏳ Blocking
Blocking مش Error.
هو ببساطة:
Transaction مستنية Transaction تانية تسيب Resource.
لكن لو الموضوع طول…
الـ Performance يتدهور.
☠️ طب والـ Deadlock؟
دي حكاية ألطف شوية 😂.
نفترض عندنا:
Order 1001Order 1002
Transaction أحمد:
Locks Order 1001↓Wants Order 1002
Transaction منى:
Locks Order 1002↓Wants Order 1001
فبقى:
Ahmed:أنا مستني منى.Mona:وأنا مستنياك.
😂
ولا واحد هيتحرك.
ده:
💀 Deadlock
SQL Server يكتشفه ويختار Transaction يعمل لها Kill.
اسمها:
Deadlock Victim
ويرجع Error.
🔥 Blocking vs Deadlock vs Optimistic Conflict
خلينا نحفظهم بالشكل ده:
| الحالة | معناها |
|---|---|
| 🔒 Blocking | واحد شغال والتاني مستنيه |
| ☠️ Deadlock | الاتنين مستنيين بعض |
| ⚡ Optimistic Conflict | محدش استنى، لكن وقت Save اكتشفنا إن الداتا اتغيرت |
ودي من أهم المقارنات اللي لازم تبقى واضحة.
🧪 طب والـ Isolation Levels؟
هنا ناس كتير بتخلط بين حاجتين مختلفتين:
Optimistic Concurrency
و:
Transaction Isolation
مش نفس الحاجة.
Isolation Level بيحدد:
Transaction تشوف إيه من Changes بتاعة Transactions تانية؟
عندك مثلًا:
READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSNAPSHOTSERIALIZABLE
لكن Optimistic Concurrency بيجاوب سؤال مختلف:
الداتا اللي المستخدم بيعدل عليها لسه نفس النسخة اللي كان قراها؟
🌟 Snapshot Isolation
SQL Server عنده Mechanism تاني مهم اسمه:
Row Versioning
بيستخدمه مع حاجات زي:
SNAPSHOT ISOLATION
و:
READ COMMITTED SNAPSHOT
الفكرة إن الـ Readers يقدروا يشوفوا Versions من الداتا بدل ما يفضلوا مستنيين Writers.
مثلًا بدل:
Writer 🔒Reader ⏳
ممكن يبقى:
Writer → Current RowReader → Previous Version
وده يقلل:
Read/Write Blocking
بشكل كبير.
لكن خلي بالك ⚠️:
Snapshot Isolation مش هو نفسه rowversion Concurrency Token.
الاتنين فيهم كلمة Version…
لكن بيحلوا مشاكل مختلفة.
🎯 Snapshot بيحل مشكلة إيه؟
غالبًا يساعد في:
Reader vs Writer Blocking
بينما rowversion عند الـ Application يساعد في:
User A vs User B Lost Update
فرق مهم جدًا.
🏦 طب هل Optimistic Concurrency مناسب لكل حاجة؟
لا.
ودي نقطة Architecturally مهمة جدًا.
نرجع للـ SuperShop.
لو بنتعامل مع:
Order NotesCustomer AddressProduct DescriptionUser Profile
Optimistic Concurrency ممتاز جدًا.
ليه؟
لأن احتمال إن اتنين يعدلوا نفس الـ Record في نفس الثانية قليل.
لكن تخيل عندك:
Product Stock = 1
وفيه 500 Customer بيحاولوا يشتروا آخر قطعة 🔥.
هنا Conflict مش Rare.
هنا عندك Hot Resource.
وممكن تحتاج Strategy مختلفة.
📦 مثال الـ Inventory
عندنا:
Stock = 1
بدل ما نعمل:
SELECT Stock
وبعدين في Application:
Stock--
وبعدين Update…
ممكن نستخدم Atomic Update:
UPDATE ProductsSET Stock = Stock - 1WHERE Id = 50AND Stock > 0;
لو:
Rows affected = 1
تمام ✅.
لو:
Rows affected = 0
يبقى:
Out of Stock
وده في بعض الحالات أقوى وأبسط من إنك تعتمد بس على rowversion.
💰 طب والفلوس؟
تخيل:
Account Balance = 1000 SAR
هنا ماينفعش تتعامل مع الموضوع بعقلية:
User Wins / Client Wins وخلاص 😅
Financial Operations محتاجة:
TransactionsAtomicityCorrect IsolationConstraintsIdempotencyProper locking strategy
وممكن حسب السيناريو تحتاج Optimistic أو Pessimistic Concurrency أو Atomic Commands.
يعني مفيش:
One Solution Fits All
🧠 إمتى أختار Optimistic Concurrency؟
فكر فيها كده:
Optimistic ممتاز لما:
Conflicts are rare
زي:
- User Profile 👤
- Order Management 📦
- CMS Articles 📝
- Product Details 🛒
- Customer Records
- Admin Screens
- CRM Data
🔒 وإمتى Pessimistic Locking يبقى منطقي؟
لما الـ Resource حساس جدًا واحتمال التنافس عليه عالي.
زي:
Limited ResourceHot RowShort critical operation
لكن بشرط:
الـ Transaction تبقى قصيرة جدًا.
مش User يفتح Form ويحجز Lock لحد ما يقرر يشرب شاي ولا قهوة 😂.
🧩 السيناريو كامل بتاعنا
تعالى نجمع قصة أحمد ومنى كلها.
البداية
Database:Order 1001Status = PendingVersion = 20
أحمد يقرأ:
Pending / V20
منى تقرأ:
Pending / V20
أحمد
يعمل:
Approved
ويبعت:
UPDATE OrdersSET Status = 'Approved'WHERE Id = 1001AND RowVersion = V20;
Success ✅.
الداتا تبقى:
Approved / V21
منى
تحاول:
Cancelled
وتبعت:
UPDATE OrdersSET Status = 'Cancelled'WHERE Id = 1001AND RowVersion = V20;
لكن:
Database = V21
فتكون النتيجة:
0 rows affected
EF Core يقول:
DbUpdateConcurrencyException
الـ API يرجع:
409 Conflict
الـ UI يقول:
⚠️ Order changed by another user.Current Status:Approved
منى تعمل Reload.
تشوف تعديل أحمد.
وبعدين تقرر:
هل فعلًا لازم Cancel؟
كده مفيش Update اتفقد في صمت.
🎉
💡 أهم Insight في الموضوع كله
لو عايز تحفظ المقال كله في جملة واحدة:
Locks بتحمي الداتا أثناء تنفيذ العمليات، لكن Optimistic Concurrency بتحميك من إنك تكتب فوق تعديل حد تاني اعتمادًا على نسخة قديمة من الداتا.
ودي نقطة فارقة جدًا.
🔥 Optimistic Concurrency مش بديل للـ Locks
ممكن النظام الواحد يستخدم:
SQL Locks+Transactions+Optimistic Concurrency+Snapshot Isolation
في نفس الوقت.
لأن كل Mechanism بيحل نوع مختلف من المشاكل.
مثلًا:
SQL Locks→ حماية العملية الحاليةTransaction→ Atomicityrowversion→ Detect stale updatesSnapshot→ Reduce reader/writer blocking
مش منافسين لبعض.
هما Tools في صندوق الأدوات بتاع الـ Architect 🧰.
📊 الخلاصة السريعة
| Concept | الفكرة |
|---|---|
| 🔒 Lock | يمنع عمليات متعارضة من الوصول لنفس Resource في نفس اللحظة |
| ⚡ Optimistic Concurrency | سيب الناس تشتغل واكتشف التعارض وقت الحفظ |
| 🛡️ Pessimistic Concurrency | اقفل الـ Resource قبل ما يحصل Conflict |
| 🧬 rowversion | Version تلقائي للسطر يساعدنا نكتشف التعديل القديم |
| ⏳ Blocking | Transaction مستنية Transaction أخرى |
| ☠️ Deadlock | Transactions مستنيين بعض |
| 📸 Snapshot Isolation | Readers يشوفوا Versions لتقليل Blocking |
| 💥 Lost Update | تعديل شخص يتم الكتابة فوقه من شخص آخر |
🎯 إمتى تستخدم إيه؟
لو عندك Web Application عادي:
ASP.NET Core+EF Core+SQL Server
وفيه CRUD Screens…
ابدأ غالبًا بـ:
Optimistic Concurrency+rowversion
خصوصًا للـ Entities اللي ممكن أكتر من User يعدلها.
لو عندك:
High ContentionMoneyInventoryLimited ResourcesCritical workflows
ساعتها لازم تبص أعمق في:
TransactionsIsolation LevelsAtomic UpdatesLocksIdempotencyBusiness Constraints
🏁 الخاتمة
الموضوع في الآخر مش:
Optimistic ConcurrencyVSSQL Locks
بالشكل الحرفي.
الأصح إنك تبص عليهم كطبقات مختلفة من الحماية.
SQL Server بيقول:
🔒 وأنا بنفذ الـ Query دلوقتي، هاحمي الداتا من العمليات المتعارضة.
والـ Application باستخدام Optimistic Concurrency بيقول:
⚡ وقبل ما أكتب، هاتأكد إن النسخة اللي المستخدم عدل عليها لسه هي آخر نسخة.
وده اللي خلّى منى بدل ما تمسح تعديل أحمد من غير ما تدري…
تاخد رسالة واضحة:
⚠️ Someone modified this Order before you.
وساعتها القرار يرجع للـ Business Logic بدل ما الـ Database تقول:
آخر واحد ضغط Save يكسب 😄
وده بالضبط الفرق بين Application بيشتغل…
وApplication مصمم صح للـ Concurrency. 🚀
Comments
Post a Comment