Transaction Isolation: MVCC, lock ও deadlock
Isolation, MVCC, and Deadlocks
এই পাঠের সূচিপত্র
এই পাঠে যা শিখবেন
- Snapshot, MVCC ও concurrent anomaly
- Row lock এবং consistent lock order
- Deadlock/serialization retry boundary
সমস্যা দিয়ে শুরু
দুই administrator একই সময়ে আলাদা row update করলেন। দুজনই আগে aggregate দেখে মনে করলেন rule বজায় আছে, শেষে combined state rule ভাঙল। Row lock শুধু touched row রক্ষা করলেও cross-row predicate invariant অরক্ষিত থাকতে পারে। Transaction থাকলেই isolation-এর সব সমস্যার সমাধান হয় না।
সহজ করে বুঝি
MVCC বহু row version রেখে reader-কে নির্দিষ্ট snapshot দেখতে দেয়। PostgreSQL Read Committed-এ সাধারণত প্রতিটি statement নতুন committed snapshot পায়; Repeatable Read transaction snapshot রাখে কিন্তু সব serialization anomaly আটকায় না। Serializable successful transactions-কে কোনো serial order-এর সমতুল্য ফল দিতে চেষ্টা করে এবং conflict হলে transaction abort হতে পারে। Engineভেদে same isolation name-এর behavior ভিন্ন হতে পারে। Deadlock হলো transaction-গুলোর পরস্পরের lock-এর অপেক্ষার cycle; database একটি abort করে cycle ভাঙতে পারে।
পরিচিত একটি উপমা
দুই clerk stock খাতার আলাদা photocopy দেখে সিদ্ধান্ত নিলেন। নিজের copy consistent হলেও দুজনের combined সিদ্ধান্ত rule ভাঙতে পারে। একাধিক খাতা lock করতে হলে সবাই একই নম্বরের ক্রমে নিলে “আমি তোমার খাতার অপেক্ষায়, তুমি আমারটির” cycle কমে।
নকশাটি দেখুন
Diagram লোড হচ্ছে…
Diagram-এর text flow দেখুন
sequenceDiagram participant A as Transaction A participant D as Database participant B as Transaction B A->>D: Lock account 1 then 2 B->>D: Request lock account 1 D-->>B: Wait A->>D: Apply transfer and commit D-->>B: Acquire locks after release
সব transfer ID-এর ascending order-এ account lock নেয়। দ্বিতীয় transaction প্রথম lock-এই অপেক্ষা করে, বিপরীত order-এর cycle কমে। এটি সব database deadlock অসম্ভব করে না; triggers, indexes বা অন্য code path আরও locks নিতে পারে। Timeout এবং bounded retry তবু দরকার।
PHP ও Laravel-এ প্রয়োগ
Action fragment: accounts table-এ id ও balance_minor integer আছে, amount positive validated, caller source account authorized। PostgreSQL/MySQL transaction ব্যবহার করুন। এটি single-currency demo, full financial ledger নয়; audit entries ও idempotency আলাদা লাগবে।
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
abort_if($fromId === $toId, 422);
DB::transaction(function () use ($fromId, $toId, $amount) {
$accounts = DB::table('accounts')
->whereIn('id', [$fromId, $toId])
->orderBy('id')->lockForUpdate()->get()->keyBy('id');
abort_unless($accounts->count() === 2, 404);
if ($accounts[$fromId]->balance_minor < $amount) {
throw ValidationException::withMessages(['amount' => 'পর্যাপ্ত balance নেই']);
}
DB::table('accounts')->where('id', $fromId)->decrement('balance_minor', $amount);
DB::table('accounts')->where('id', $toId)->increment('balance_minor', $amount);
}, attempts: 3);সুবিধা, সীমাবদ্ধতা ও trade-off
Stronger isolation reasoning সহজ করতে পারে, কিন্তু conflicts ও retry cost বাড়ায়। Row locks নির্দিষ্ট contention serialize করে; long transaction throughput কমায়। Optimistic version checks low-conflict workload-এ useful, কিন্তু caller-কে conflict retry বা reject সামলাতে হয়। Database-specific behavior integration test ছাড়া শুধু SQLite test দিয়ে production correctness দাবি করবেন না।
সাধারণ ভুল
- Transaction closure-এ email/payment side effect করে retry-তে duplicate করা।
- সব code path আলাদা lock order নেওয়া।
- Repeatable Read সব engine-এ exactly same ভাবা।
আরও একটু গভীরে
Serialization failure-এ শুধু শেষ statement নয়, পুরো transaction নতুন snapshot-এ retry করতে হয়। Laravel attempts parameter concurrency-related errors retry করতে পারে; কোন error mapped হয় framework/driver অনুযায়ী পরীক্ষা করুন। Retry budget finite রাখুন এবং exhausted conflict report করুন। Gap/range locks, predicate locks ও MVCC cleanup engine-specific। Long-lived snapshot vacuum/cleanup ও storage pressure-এ প্রভাব ফেলতে পারে।
মনে রাখুন
Isolation guarantee engine-specific পড়ুন। Consistent lock order ও ছোট transaction contention কমায়। Retry-safe closure-এ database change রাখুন, external side effect আলাদা workflow-এ নিন।
নিজে চেষ্টা করুন
দুটি independent PostgreSQL connection দিয়ে opposite transfer চালান। Lock order reverse করে deadlock observe করুন, তারপর consistent order দিন। প্রতিবার total balance invariant ও retry attempts পরীক্ষা করুন।
নিজের বোঝা যাচাই করুন
1. MVCC মানে lock লাগে না?
না। Readers snapshots ব্যবহার করলেও writes conflict, uniqueness ও explicit row locks থাকতে পারে। MVCC concurrency-এর একটি mechanism।
2. Serializable-এ error কেন?
সব successful transaction serial order-এর মতো রাখার জন্য conflicting transaction abort হতে পারে। Application whole transaction retry সামলাবে।
3. OrderBy lock কেন?
সব caller একই resource order নিলে common deadlock cycle কমে। অন্য locks থাকায় absolute deadlock-free guarantee নয়।
4. Retry closure email পাঠাবে?
না। Closure আবার চাললে email duplicate হতে পারে। Commit-এর পর reliable handoff ও idempotent consumer লাগতে পারে।
5. Repeatable Read universal?
না। PostgreSQL ও MySQL implementation-এর details আলাদা। নামের পাশাপাশি product/version docs ও concurrency tests দরকার।
আরও পড়ুন
এই পাঠের কুইজ
এই পাঠ থেকে ২টি প্রশ্ন। উত্তর দিয়ে ব্যাখ্যা দেখুন; অধ্যায়ের কুইজ আলাদাভাবে দিতে হবে।
এই পাঠের ২টি প্রশ্নের উত্তর দিন। পাস করতে দুটিই সঠিক হতে হবে।
জমা দেওয়ার পর সঠিক উত্তর ও ব্যাখ্যা দেখবেন। ফলাফল এই ব্রাউজারে সংরক্ষিত থাকবে। অনুশীলনের ফল অধ্যায় সম্পন্নের হিসাবে যোগ হয় না।
অগ্রগতি এই ব্রাউজারে সংরক্ষিত থাকে। শুধু পাঠ খুললে সম্পন্ন হয় না।