Locking ও 2PL — strict two-phase আর deadlock-এর খেলা
Locking and Two-Phase Locking
Lock-এর শৃঙ্খলা: S/X আর intention-matrix থেকে two-phase rule-এর proof-sketch, strict 2PL-এর cascading-abort নিষেধ, wait-for graph-এ বৃত্ত খুঁজে victim বাছাই — pg_locks-এ সব live।
আগে এটা বুঝি
পরশ লেসনে snapshot — optimistic পথ। আজ classical পথ — pessimistic: আগে permission, তারপর কাজ। Database কড়া librarian: বই নিতে টোকেন; দুইজন একই বইয়ে শুধু পড়লে একসাথে; লিখতে চাইলে বাকি সবাই বাইরে।
কিন্তু lock-এর ছন্দটাই আসল গণিত: যখন-তখন lock নিলে serializable schedule হাতছাড়া; আগ্রহে নিলে দুই tx একে অপরের জিনিসে হাত রেখে চিরতরে আটকে যেতে পারে — deadlock, race-condition-লেসনের database-ভাই।
তিনটা প্রশ্ন: (১) কোন lock কার সাথে compatible? (২) কোন ছন্দে নিলে correctness গ্যারান্টি? (৩) ছন্দ মানলেও আটকে গেলে — কে, কখন, কীভাবে উদ্ধার করবে?
মূল ধারণা
Shared আর Exclusive — দুই ধরনের অনুমতি
| Lock | অনুমতি | উদাহরণ |
|---|---|---|
| Shared (S) | পড়া — একসাথে অনেকে | SELECT ... FOR SHARE |
| Exclusive (X) | পড়া+লেখা — একা | FOR UPDATE, UPDATE, DELETE |
Compatibility matrix — মুখস্থ না, যুক্তি:
| S | X | |
|---|---|---|
| S | ✔ | ✗ |
| X | ✗ | ✗ |
এক বাক্যে: দুই reader একে অপরকে ক্ষতি করে না; writer সবার একমাত্র। এই ২×২ grid-ই concurrency-র সব রাজনীতির জন্মস্থান — reader-দলবদল, writer-নির্বাসন।
Two-Phase Locking — ছন্দটাই theorem
- Growing phase: শুধু lock নেওয়া — ছাড়া নিষেধ
- Shrinking phase: একবার একটাও ছাড়লে — নতুন lock নেওয়া চিরতরে বন্ধ
Strict 2PL — commit পর্যন্ত ধরে রাখা
Plain 2PL-এ shrinking phase-এ commit-এর আগেই lock ছাড়া যায়। সমস্যা: cascading abort —
t1 T1: w(A); unlock(A)
t2 T2: read A (T1-এর uncommitted data!); w(A)
t3 T1: ভুল ধরা পড়ল → ROLLBACK
t4 T2-ও ROLLBACK-বাধ্য ← T2 ছিল T1-এর উপর দাঁড়ানো
t5 T3 যদি T2-এর data পড়ত... ← ধারাবাহিক পতনঔষধ: Strict 2PL — write-lock commit/abort পর্যন্ত ধরে রাখা; uncommitted write-এর উপর কেউ দাঁড়াতেই পারে না। Production engine প্রায় সবাই strict।
Intention lock — table-level fast check
Table-X চাওয়া tx কি ১০ কোটি row ঘেঁটে দেখবে কোথাও row-lock আছে কি না? অসম্ভব। সমাধান: row-lock-এর আগে ancestor-এ ইঙ্গিত-চিহ্ন:
| Mode | মানে |
|---|---|
| IS | ভেতরে S-lock নেওয়ার ইচ্ছা আছে |
| IX | ভেতরে X-lock নেওয়ার ইচ্ছা আছে |
| SIX | পুরোটা পড়ছি (S), ভেতরে লিখবও (IX) |
Multi-level matrix (table↔row সব স্তরে এক):
| IS | IX | S | SIX | X | |
|---|---|---|---|---|---|
| IS | ✔ | ✔ | ✔ | ✔ | ✗ |
| IX | ✔ | ✔ | ✗ | ✗ | ✗ |
| S | ✔ | ✗ | ✔ | ✗ | ✗ |
| SIX | ✔ | ✗ | ✗ | ✗ | ✗ |
| X | ✗ | ✗ | ✗ | ✗ | ✗ |
Intention mode data-access-এর অনুমতি দেয় না — “ভেতরে কেউ আছে”-র
সংকেত; table-X চাইলে IX দেখেই O(1)-এ ব্লক। PG-র pg_locks-এ
এই modes-পরিবার (AccessShareLock…) সরাসরি দেখা যায়।
Granularity — কত মোটা তালাবন্ধ
| Granularity | lock সংখ্যা | concurrency | overhead |
|---|---|---|---|
| Table | ১ | প্রায় শূন্য | সবচেয়ে কম |
| Page | page-প্রতি | মাঝারি | মাঝারি |
| Row | tuple-প্রতি | সর্বোচ্চ | memory + bookkeeping সর্বোচ্চ |
Coarse = সস্তা-কিন্তু-সব-ব্লক; fine = মুক্ত-কিন্তু-ব্যয়বহুল। Bulk operation মোটা grain, OLTP hot-row fine; tx অতিরিক্ত fine lock জমালে engine মাঝে মাঝে জোর-করে মোটা grain-এ escalate করে — memory বাঁচাতে concurrency বলি দিয়ে।
Deadlock — দুই দল সমাধান
OS-লেসনের four-condition গণিত, নতুন পোশাকে।
Prevention — বৃত্ত-গঠনই অসম্ভব করা:
- Resource ordering: সব tx একই ক্রমে lock নেবে (account-id ascending?) — circular-wait condition-ই ভাঙে; application-discipline, engine বলিয়ে দেয় না
- Timeout: সময়সীমায় নিজেই হাল ছাড়ো — সরল, false positive-এ সুস্থ tx-ও মরে
Detection — বৃত্ত হোক, ধরব:
- Engine wait-for graph রাখে: node = tx, edge = “T1 T2-এর lock- এর জন্য অপেক্ষায়”
- মাঝে মাঝে graph-এ cycle খোঁজা — cycle = deadlock
- Cycle-এর একজন victim abort — বাছাই সাধারণত সস্তা-victim heuristic: যার undo-cost কম
PG detection পছন্দ করে; deadlock_timeout default ১ সেকেন্ড —
detector-এর খরচ real (waiter-হয়ে lock-table scan), আর বেশিরভাগ block
১s-আগেই মুক্ত; তাড়াহুড়োয় healthy waiting-ও মরত।
ভেতরে কী ঘটছে
pg_locks — lock-table-এর জানালা
SELECT pid, locktype, relation::regclass AS rel, mode,
granted, waitstart
FROM pg_locks WHERE relation IS NOT NULL
ORDER BY granted, waitstart NULLS FIRST; pid | locktype | rel | mode | granted
-------+----------+---------+------------------+---------
4101 | relation | accounts| RowExclusiveLock | t
4102 | relation | accounts| RowExclusiveLock | f ← অপেক্ষমাণ!granted = false লাইনগুলোই live contention; pg_stat_activity join-এ
কে কোন query-তে আটকে সব দেখা যায় — production debugging-প্রথম query।
Deadlock detector-এর এক মিনিট
deadlock_timeout (1s default) পার হলে প্রতিটা waiter নিজের হয়ে detector
জাগায়: wait-edge থেকে DFS — নিজের কাছে ফিরে এলে cycle, victim-প্রার্থী।
Victim-বাছাই PG-তে মোটামুটি সস্তা-victim: যার undo কম। Error message-এ
conflicting process-দের pid-সহ বর্ণনা থাকে — experiment-এ দেখবেন।
lock_timeout বনাম deadlock_timeout
lock_timeout | deadlock_timeout | |
|---|---|---|
| কার setting | আপনার session | server global |
| কী করে | এত ms lock না পেলে হাল ছাড়ো | এত ms পরে detector জাগবে |
| দর্শন | prevention | detection-সময়সূচি |
| default | 0 (অসীম!) | 1000 ms |
প্রথমটা migration-script-এ প্রায় বাধ্যতামূলক — নইলে long transaction- পেছনে DDL দাঁড়িয়ে app-freeze; দ্বিতীয়টা ছোঁয়া প্রায় কখনো দরকার হয় না।
উদাহরণ
পূর্ণ trace — AB-BA deadlock জন্মানো আর কাটা
T1 A→B ক্রমে, T2 B→A ক্রমে:
সময় T1 T2 lock-state
──── ─────────────────────── ───────────────── ─────────────────
t1 BEGIN; BEGIN;
t2 UPDATE a SET v=1 UPDATE b SET v=1
[X(a) ✓] [X(b) ✓]
t3 UPDATE b SET v=2 UPDATE a SET v=2
[X(b)? BLOCKED] [X(a)? BLOCKED]
(T2 holds b) (T1 holds a)
t4 wait-for: T1→T2 wait-for: T2→T1 ← CYCLE!
t5 detector জাগে (≥1s)
t6 ERROR: deadlock detected! ← T1 victim
t7 [X(a) ✓] ← T2 unblocked
t8 COMMIT ← সম্পূর্ণশিক্ষা: (১) কোনো statement “ভুল” ছিল না — দুই বৈধ program-এর
interleaving-ই বৃত্ত বানাল; (২) victim engine-এর বাছাই; (৩) T2-এর চোখে
কিছুই ঘটেনি — শুধু দেরি। Deadlock victim-চোখে error, অন্যের চোখে delay।
জবাব: SQLSTATE 40P01-এ whole-tx retry; root-fix ordering — দুইজনেই
a-আগে-b নিলে বৃত্ত-গঠনই অসম্ভব।
ছবিতে
Compatibility আর wait-for — দুইটা map
COMPATIBILITY:
IS IX S SIX X নিয়ম: intention-রা পরস্পর-
IS ✔ ✔ ✔ ✔ ✗ বন্ধু; S-এর শত্রু IX/SIX/X;
IX ✔ ✔ ✗ ✗ ✗ X-এর শত্রু সবাই
S ✔ ✗ ✔ ✗ ✗
SIX ✔ ✗ ✗ ✗ ✗
X ✗ ✗ ✗ ✗ ✗
WAIT-FOR GRAPH (AB-BA):
┌── T1 wants X(a) held by T2 ◄──┐
│ │
└── T2 wants X(b) held by T1 ───┘ ← বৃত্ত = deadlock
victim=T1 abort → T1-এর lock release → T2 grant → এগোয়নিজে চালিয়ে দেখুন
pg_locks-এ অপেক্ষা দেখা, AB-BA deadlock জন্ম দেওয়া, দুই timeout-এর ফারাক
Setup: docker run --rm -d --name pg-lab -e POSTGRES_PASSWORD=lab -p 5432:5432 postgres:16;
দুই terminal-এ docker exec -it pg-lab psql -U postgres; Terminal A:
CREATE TABLE accounts (id text PRIMARY KEY, bal numeric);
INSERT INTO accounts VALUES ('a', 1000), ('b', 1000);অংশ ১ — contention live দেখা:
Terminal A Terminal B
───────────────────────────────────── ──────────────────────────
BEGIN;
UPDATE accounts SET bal=999 WHERE id='a';
BEGIN;
UPDATE accounts SET bal=555
WHERE id='a'; -- ঝুলে গেল!তৃতীয় psql session:
SELECT pid, mode, granted FROM pg_locks
WHERE relation = 'accounts'::regclass;
-- pid | mode | granted
-- ------+------------------+---------
-- 4101 | RowExclusiveLock | t
-- 4102 | RowExclusiveLock | f ← B অপেক্ষায়!A-তে ROLLBACK — B সঙ্গে সঙ্গে এগোবে। অপেক্ষা ছিল নীরব, pg_locks-এ
পুরোটা লেখা ছিল।
অংশ ২ — AB-BA deadlock জন্ম দেওয়া:
Terminal A Terminal B
───────────────────────────────────── ──────────────────────────
BEGIN; BEGIN;
UPDATE ... SET bal=bal-10 WHERE id='a';
UPDATE ... SET bal=bal-10
WHERE id='b';
UPDATE ... SET bal=bal-20 WHERE id='b';
-- (BLOCKED) UPDATE ... SET bal=bal-20
WHERE id='a';
-- (BLOCKED) ← বৃত্ত!
-- ~১ সেকেন্ড পরে A-তে:
ERROR: deadlock detected
DETAIL: Process 4101 waits for ... blocked by 4102.
Process 4102 waits for ... blocked by 4101.
HINT: See server log for query details.
-- B এখন এগোয়; দুই জায়গায় COMMITError এক জায়গাতেই — victim engine-এর। Retry 40P01 ধরে।
অংশ ৩ — দুই timeout, দুই আচরণ:
Terminal A hold করুক: BEGIN; UPDATE accounts SET bal=42 WHERE id='a';
(COMMIT এখনো না)। Terminal B — প্রথমে prevention-style:
BEGIN;
SET LOCAL lock_timeout = '2s';
UPDATE accounts SET bal=7 WHERE id='a';
-- ঠিক ~2 সেকেন্ডে:
ERROR: canceling statement due to lock timeout
ROLLBACK;এবার detection-style: B আবার ব্লক করুন (timeout-set ছাড়া), A-কেও B-
এর ধরা row-র পেছনে পাঠিয়ে বৃত্ত বানান — এবার ~1s-এ detector হাল
ছাড়ায়, ভিন্ন error (deadlock detected), ভিন্ন mechanism। নোট করুন:
2s-এ নিজের সিদ্ধান্ত বনাম 1s-এ engine-এর রায় — দুই clock, দুই
দর্শন।
Contention-এ pg_locks granted/waiting live দেখায়, AB-BA pattern নিয়মিতভাবে deadlock-error আর victim তৈরি করে, আর lock_timeout (prevention) আর deadlock_timeout (detection) ভিন্ন সময়ে ভিন্নভাবে হাল ছাড়ায়।
নিজে বানান
LockManager — intention hierarchy, wait-for graph, victim selection সহ
- Resource tree model করুন: db/table/row — প্রতিটা level-এ lock-table
- S/X-এর সাথে IS/IX/SIX mode; acquire-এ ancestors-এ intention auto-grant
- Compatibility matrix দিয়ে grant/block; blocked হলে wait-for edge
- DFS cycle-detection; victim cost-heuristic-এ (সবচেয়ে কম lock-ধারী)
- Interleaved script replay করে grant/block/abort timeline ছাপুন
লক্ষ্য: engine lock-manager-এর মেরুদণ্ড — multi-granularity compatibility + deadlock detection — নিজের হাতে।
from collections import defaultdict
COMPAT = { # সব স্তরে একই — intention সংকেত, X একা
'IS': {'IS':True,'IX':True,'S':True,'SIX':True,'X':False},
'IX': {'IS':True,'IX':True,'S':False,'SIX':False,'X':False},
'S': {'IS':True,'IX':False,'S':True,'SIX':False,'X':False},
'SIX':{'IS':True,'IX':False,'S':False,'SIX':False,'X':False},
'X': {'IS':False,'IX':False,'S':False,'SIX':False,'X':False},
}
ANCESTOR = {'S': ['IS'], 'X': ['IX']} # row-request-এর ইঙ্গিত-chain
class LockManager:
def __init__(self, log=print):
self.held = defaultdict(list) # res -> [(tx, mode)]
self.waits = {} # tx -> (res, mode)
self.log = log
def _ok(self, res, tx, mode):
return all(COMPAT[mode][m] for t, m in self.held[res] if t != tx)
def _anc(self, res):
parts = res.split('/') # যেমন db/accounts/a
return ['/'.join(parts[:i]) for i in range(1, len(parts))]
def acquire(self, tx, res, mode):
hints = dict(zip(self._anc(res),
ANCESTOR[mode]*len(self._anc(res))))
for anc, hint in hints.items():
if not self._ok(anc, tx, hint):
self.log(f" T{tx} blocked on {anc}[{hint}]")
return False
if (tx, hint) not in self.held[anc]:
self.held[anc].append((tx, hint))
self.log(f" T{tx} granted {anc}[{hint}] (intention)")
if self._ok(res, tx, mode):
self.held[res].append((tx, mode))
self.log(f" T{tx} granted {res}[{mode}]")
return True
self.waits[tx] = (res, mode)
self.log(f" T{tx} WAITS for {res}[{mode}]")
return False
def release_all(self, tx):
n = 0
for res in list(self.held):
n += len([1 for t,_ in self.held[res] if t == tx])
self.held[res] = [(t,m) for t,m in self.held[res] if t != tx]
self.waits.pop(tx, None)
self.log(f" T{tx} released all ({n} locks)")
def find_cycle(self):
edge = defaultdict(set)
for ta, (res, _) in self.waits.items():
for tb, _m in self.held[res]:
if tb != ta: edge[ta].add(tb)
seen = set()
def dfs(u, path):
if u in path: return path[path.index(u):]+[u]
if u in seen: return None
seen.add(u)
for v in sorted(edge[u]):
c = dfs(v, path+[u])
if c: return c
for n_ in sorted(set(edge) | set(self.waits)):
c = dfs(n_, [])
if c: return c
return None
def resolve_deadlocks(self):
victims = []
while (cyc := self.find_cycle()):
members = cyc[:-1]
# cost-heuristic: সবচেয়ে কম lock-ধারী = সস্তা undo
victim = min(members, key=lambda t: sum(
len([1 for tt,_ in self.held[r] if tt==t])
for r in self.held))
self.log(f" DEADLOCK cycle "
f"{'->'.join('T%d'%t for t in cyc)} — victim T{victim}")
self.release_all(victim)
victims.append(victim)
return victims
print("AB-BA interleaving:")
lm = LockManager(log=lambda s: print(s))
lm.acquire(1, 'db/accounts/a', 'X')
lm.acquire(2, 'db/accounts/b', 'X')
lm.acquire(1, 'db/accounts/b', 'X') # blocks (T2 holds b)
lm.acquire(2, 'db/accounts/a', 'X') # blocks (T1 holds a) — বৃত্ত!
lm.resolve_deadlocks()
lm.acquire(2, 'db/accounts/a', 'X') # victim গেল — grant
lm.release_all(2)
print("Reader-ভিড় আর table-X:")
lm2 = LockManager(log=lambda s: print(s))
for r in (1, 2, 3): lm2.acquire(r, 'db/reports/r1', 'S')
lm2.acquire(9, 'db/reports', 'X') # table-X: সবার সাথে conflict
lm2.resolve_deadlocks() # cycle নেই — শুধু অপেক্ষা
lm2.release_all(1)প্রত্যাশিত output (সংক্ষেপে):
AB-BA interleaving:
T1 granted db[a?]... intentions...
T2 WAITS for db/accounts/a[X]
DEADLOCK cycle T1->T2->T1 — victim T1
T1 released all (N locks)
T2 granted db/accounts/a[X]
Reader-ভিড় আর table-X:
T9 WAITS for db/reports[X] ← cycle নেই, detector নীরবদুই শিক্ষা output-এই: (১) victim সবসময় “সবচেয়ে সস্তা”; (২) অপেক্ষা deadlock না — detector শুধু বৃত্তে হস্তক্ষেপ করে।
নিজে বাড়ান: (১) per-resource FIFO queue + jump-নিষেধ (fairness); (২) victim-heuristic বদল — youngest-tx, কোনটা কখন ভালো? (৩) ৮+ row-lock ধরলে page-level escalation — concurrency-র কী হারাল দেখুন।
বাস্তব সিস্টেমে
Migration-এর ভয়: ACCESS EXCLUSIVE। ALTER TABLE সর্বোচ্চ lock
নেয়, আর তার পেছনে দাঁড়ায় আগের সব long query — ৩০s migration app-freeze।
ঔষধ: শুরুতেই SET lock_timeout='2s' + retry — hood-জ্ঞানের production-রূপ।
Advisory locks। pg_advisory_lock(42) — engine-grade locking আপনার
মনগড়া resource-এ (scheduler leader-election, cache-warmup)। Distributed
cron-এ দুই instance এক job না নেওয়ার সস্তাতম সমাধানগুলোর একটা।
InnoDB gap lock। MySQL RR range-query পাশের ফাঁকাও lock করে (next-key locking) — phantom-প্রতিকার, দাম insert-heavy contention। PG snapshot-পথে জটিলতাটাই নেই — দুই design-দর্শনের প্রতিদিন-অনুভূত ফারাক।
Deadlock-বিহীন জীবনের রেসিপি। Consistent access-order, ছোট tx,
index-যুক্ত query, 40P01-এ retry — চারটাতেই deadlock production-এ বিরল।
যে ভুলগুলো সবাই করে
“PostgreSQL-এ SELECT সবসময় row-lock নেয়।”
Plain SELECT MVCC snapshot থেকে পড়ে — কোনো row-lock ছাড়াই;
readers-never-block-writers promise এখানেই (MVCC লেসনে পূর্ণ)। Row-lock
শুধু explicit FOR UPDATE/FOR SHARE বা actual UPDATE/DELETE-এ। Textbook-
এর readers-take-S-lock model PG-র বাস্তবতায় খাটে না।
“Deadlock মানে bug — সঠিক কোডে deadlock হয় না।”
দুই বৈধ, স্বাধীনে-সঠিক program-এর interleaving-ই example-এ বৃত্ত বানাল। তাই detector built-in, retry normal flow-র অংশ। Ordering-discipline deadlock বিরল করে — অসম্ভব নয়।
“Intention lock তৃতীয় একটা lock-kind — S/X-এর মতো নতুন semantic।”
Intention mode নিজে data-access অনুমতি দেয়ই না — hierarchy-র সংকেত মাত্র (“নিচে finer-grain lock আসছে”), যাতে table-conflict check O(1) হয়। Multi-level tree-indexing-trick, lock-এর version — নতুন semantics নয়।
“deadlock_timeout কমালেই deadlock দ্রুত সমাধান — 10ms করে দিই।”
Detector জাগানোর খরচ real — প্রতিটা timeout-পার waiter lock-table scan
করে; healthy waiting-ও 10ms পার হয়। 1s default measurement-ভিত্তিক
compromise; application-side দ্রুত হালছাড়া চাইলে সেটা lock_timeout-এর
কাজ — ভিন্ন যন্ত্র।
বুঝেছেন কি না দেখুন
1S/X matrix-এ একমাত্র compatible pair কোনটা, যুক্তি?
স্মরণ
S–S। পড়া idempotent — দুই reader একে অপরের দেখা ডেটা বদলায় না; লেখা কারো সাথে সহাবস্থান করতে পারে না (stale-read/lost-write)। Readers ভিড় করে, writer একা।
2Growing/shrinking বিভাজন কেন conflict-serializability দেয় — ordering-
argument তিন বাক্যে।
যুক্তি
Conflict-item-এ acquisition definite ক্রম বসায় (আগে-পাওয়া আগে)। Shrinking- phase-এ ফেরত-দরজা বন্ধ — কোনো tx ক্রম উল্টে দিতে পারে না। সব pair-এর ক্রম acyclic precedence-graph দেয় — topological serial order মানেই conflict-equivalent schedule।
3T1 লিখে lock ছেড়ে দিল, T2 সেই data পড়ে এগোল, তারপর T1 rollback। কী
ঘটবে — সমস্যার নাম, ঔষধ?
প্রয়োগ
T2-কেও abort করতে হবে (uncommitted data-র উপর দাঁড়ানো); ধারাবাহিকতায় T3-ও — cascading abort। ঔষধ Strict 2PL: write-lock commit/abort পর্যন্ত ধরা — cascade-ই অসম্ভব।
4Table-X-চাওয়ার সময় intention lock O-notation-এ কোন খরচ বাঁচায়?
যুক্তি
O(n) row-ঘেঁটে-দেখা → O(1) table-level compatibility-check। Intention-entry “ভেতরে activity আছে”-র summary-bit — constant-time conflict-detection।
5AB-BA-তে victim T1। T2-এর client কি error দেখবে? Retry কার দায়িত্ব?
প্রয়োগ
T2 কিছুই দেখে না — সামান্য দেরির পরে স্বাভাবিক এগোনো। Retry victim- এর দায়: T1-এর client SQLSTATE 40P01 পেয়ে whole-transaction আবার চালাবে — partial-resume কখনো নয়।
6Batch-job ১ কোটি row update করে — lock-memory বিস্ফোরক। Granularity-
দৃষ্টিতে দুই কৌশল, দাম-সহ।
ডিজাইন
(১) Chunking + per-chunk commit: peak lock-count সীমিত; দাম — chunk-মাঝে
partial progress RC-reader-এ দৃশ্যমান। (২) LOCK TABLE EXCLUSIVE: একটাই
lock, memory নগণ্য; দাম — পুরো টেবিল বন্ধ। Batch-window থাকলে (২), online-
traffic-এ (১)।
এরপর কী
এরপর কী
Classical পথ শিখলাম: lock, ছন্দ, বৃত্তে victim। কিন্তু অস্বস্তি রয়ে গেল — experiment-এ plain SELECT কেন block করলই না? কারণ PG read-path- এ এই lock-jagat ব্যবহারই করে না; সে চালায় আরেকটা ইঞ্জিন।
পরের লেসনে — MVCC: PostgreSQL-এর বাস্তব implementation:
- xmin/xmax tuple-header আর visibility function-এর পূর্ণ গণিত
- INSERT/DELETE/UPDATE storage-এ কী রেখে যায় — dead tuple-এর জন্ম
- Snapshot timing, clog lookup, HOT update-এর index-bypass
- Long-running tx-এর বিষ — bloat আর xid wraparound; VACUUM-এর অপরিহার্যতা; InnoDB-র undo-log পথ
আজ lock-শৃঙ্খলা; পরের লেসনে version-রাজনীতি — বেছে-নেওয়ার দাম।
আরও পড়ুন
- Database System Concepts — Chapter 15: Concurrency Control (15.1–15.2) — Abraham Silberschatz, Henry F. Korth, S. Sudarshan · 2PL-এর আনুষ্ঠানিক সংজ্ঞা, serializability-proof, graph-based protocol — theory-র ভিত্তি
- The notions of consistency and predicate locks in a database system (CACM 1976) — K. P. Eswaran, J. N. Gray, R. A. Lorie, I. L. Traiger · 2PL ও serializability-সম্পর্কের ঐতিহাসিক মূল paper — theorem-টা এখানেই প্রথম
- PostgreSQL Documentation — 13.3: Explicit Locking / 13.4: Application-Level Data Checking — The PostgreSQL Global Development Group · pg_locks, FOR UPDATE, deadlock_timeout — experiment section-এর সব tool-এর প্রামাণ্য বর্ণনা