সরাসরি পর্বে যান
পর্ব ৪৫ · ৬৭-এর মধ্যে · মডিউল ৭

Schedule আর serializability — conflict আর view

Schedules and Serializability — Conflict and View Equivalence, Precedence Graphs and Recoverability

Taught in Bangla, this lesson covers schedules and serializability: serial and non-serial schedules, conflicting operations, precedence graphs and the conflict-serializability theorem, finding an equivalent serial order with topological sort (shown with a live SQL recursive CTE and a JavaScript checker), view serializability and blind writes, recoverable, cascadeless and strict schedules, cascading aborts, and fully worked exam-style problems.

এই পর্বে যা শিখবেন

  • Schedule লেখার চিহ্ন (R1(A), W2(B), C1), serial আর non-serial schedule, আর কেন DBMS পালা করে চালায়
  • Conflicting operation, অদলবদল করে serial বানানো, আর precedence graph-এর উপপাদ্য — প্রমাণসহ
  • Topological sort দিয়ে সমতুল্য serial ক্রম — লাইভ SQL-এর recursive CTE আর একটি JavaScript চেকারে
  • View serializability, blind write, আর কেন বাস্তবে এটি যাচাই করা হয় না
  • Recoverable, cascadeless আর strict schedule; cascading abort; আর 2PL কীভাবে এগুলো নিশ্চিত করে

১ · Schedule — অনেক transaction-এর কাজ এক সারিতে

পর্ব ৪৪-এ দেখেছেন, একটি transaction কয়েকটি কাজের এমন একটি দল, যা হয় পুরোটা হবে, নয়তো একটুও না — ACID-এর চারটি গুণ নিয়ে। সেখানে “I” মানে isolation: একসাথে চললেও প্রতিটি transaction যেন একা চলছে। এই পর্বে প্রশ্নটি নিখুঁত করা হবে: “একা চলছে যেন” কথাটির গাণিতিক মানে কী, আর কোনো নির্দিষ্ট ঘটনাক্রম সেই শর্ত মানে কি না, তা কীভাবে যাচাই করবেন।

তত্ত্বে DBMS একটি transaction-কে দেখে শুধু কয়েকটি মৌলিক অপারেশন হিসেবে; ভেতরের হিসাব (যোগ, বিয়োগ) সে জানে না। চিহ্নগুলো:

  • R1(A) — transaction T1 ডেটা item A পড়ে (read). item মানে একটি সারি, একটি কলামের মান বা একটি page — যে স্তরে DBMS নজর রাখে।
  • W2(B) — T2 item B-তে লেখে (write).
  • C1 — T1 commit করে; A1 — T1 abort করে, অর্থাৎ rollback.

একটি schedule হলো কয়েকটি transaction-এর সব অপারেশনের একটি সারি, যেখানে প্রতিটি transaction-এর নিজের অপারেশনগুলোর ক্রম অটুট থাকে। যদি একটি transaction পুরো শেষ হওয়ার পরেই পরেরটি শুরু হয়, সেটি serial schedule. অন্যথায় non-serial — অপারেশনগুলো interleaving-এ, অর্থাৎ পালা করে মেশানো। যে schedule-এ প্রতিটি transaction-এর শেষ অপারেশন C বা A, তাকে বলে সম্পূর্ণ (complete) schedule; অংশ ৭-এর আগে পর্যন্ত আমরা commit-এর কথা বাদ রেখে শুধু read আর write দেখব, কারণ serializability-র প্রশ্নে সেগুলোই নির্ধারক।

ট্রেনের টিকিট বুকিংয়ের কাউন্টারে দুজন কর্মী একই খাতা দেখে আসন দেন। দুজনেই খাতায় দেখলেন ১২ নম্বর আসন খালি, দুজনেই দুই যাত্রীকে সেটি দিলেন, তারপর দুজনেই খাতায় নিজের যাত্রীর নাম লিখলেন। প্রতিটি কর্মী একা কাজ করলে নিয়ম মানতেন; ভুলটি হয়েছে দুজনের “পড়া” আর “লেখা” কোন ক্রমে মিশেছে, তা থেকে। schedule-এর তত্ত্ব ঠিক এই ক্রমটিকে লিখে রাখে আর বিচার করে।

এই পর্বের চলমান উদাহরণ একটি মোবাইল ওয়ালেটের দুটি লেনদেন। অ্যাকাউন্ট A-তে ১,০০০ টাকা, B-তে ২,০০০. T1: A থেকে B-তে ১০০ টাকা পাঠায়। T2: A থেকে B-তে ৫০ টাকা পাঠায়। প্রতিটি transaction চারটি অপারেশন: R(A), W(A), R(B), W(B). ফোনের পর্দায় পাশাপাশি কলাম না এঁটে যায়, তাই schedule লেখা হবে খাড়া টেবিলে — প্রতিটি transaction-এর একটি কলাম, প্রতিটি সারিতে একটি অপারেশন, ওপর থেকে নিচে সময়।

#Serial: T1, T2S: পালা করেS′: পালা করে
১R1(A)R1(A)R1(A)
২W1(A)W1(A)R2(A)
৩R1(B)R2(A)W1(A)
৪W1(B)W2(A)W2(A)
৫R2(A)R1(B)R1(B)
৬W2(A)W1(B)W1(B)
৭R2(B)R2(B)R2(B)
৮W2(B)W2(B)W2(B)
শেষেA ৮৫০ · B ২,১৫০A ৮৫০ · B ২,১৫০A ৯৫০ · B ২,১৫০

মোটা অক্ষরগুলো T2-এর সেই অপারেশন, যা serial ক্রম থেকে আগে সরে এসেছে। S-এ T2 A পড়ে T1 লেখার পরে (৯০০), লেখে ৮৫০ — সব ঠিক। S′-এ T2 A পড়ে T1 লেখার আগে: দুজনেই ১,০০০ পড়ে, T1 লেখে ৯০০, তারপর T2 লেখে ৯৫০ — T1-এর কাটা ১০০ টাকা মুছে গেছে। দুই অ্যাকাউন্টের মোট টাকা ৩,০০০ থেকে হয়েছে ৩,১০০; শূন্য থেকে ১০০ টাকা তৈরি। এটিই পর্ব ৪৪-এর lost update.

২ · কেন পালা করে চালানো — আর লক্ষ্যটি কী

সবচেয়ে নিরাপদ পথ তো serial — একটির পর একটি। তবু কোনো বড় DBMS তা করে না, কারণ একটি transaction-এর বেশিরভাগ সময় কাটে অপেক্ষায়: ডিস্ক থেকে page আসার, নেটওয়ার্কে অ্যাপের পরের নির্দেশের, বা লগ ডিস্কে লেখা শেষ হওয়ার। সেই সময় CPU বসে থাকলে অপচয়।

পাড়ার একটি দর্জির দোকান। একজন দর্জি সারি ধরে কাজ করলে প্রথম গ্রাহকের পাঞ্জাবির কাপড় ধোয়া-শুকানোর সময়ও বসে থাকতেন। বাস্তবে তিনি সেই ফাঁকে দ্বিতীয় গ্রাহকের মাপ নেন, তৃতীয়জনের বোতাম লাগান। দিনে বেশি কাজ শেষ হয় — শর্ত একটাই: এক গ্রাহকের কাপড় যেন অন্য গ্রাহকের মাপে না কাটা হয়। DBMS-এর interleaving এই দর্জির কৌশল, আর serializability সেই শর্ত।

  • Throughput বাড়ে: এক transaction I/O-র অপেক্ষায় থাকলে CPU অন্যটির কাজ করে; প্রতি সেকেন্ডে বেশি transaction শেষ হয়।
  • অপেক্ষার সময় কমে: মাসিক হিসাবের একটি লম্বা রিপোর্ট চলার সময় একটি ছোট পেমেন্ট তার পেছনে মিনিটের পর মিনিট আটকে থাকে না।
  • বহু CPU core কাজে লাগে: serial ক্রমে একসাথে একটির বেশি transaction চলতেই পারে না।

কিন্তু S′ দেখিয়েছে, যেকোনো মিশ্রণ নিরাপদ নয়। তাহলে কোনটি নিরাপদ? যুক্তিটি সরল। ধরে নেওয়া হয়, প্রতিটি transaction একা চললে ডেটাবেসকে একটি সঠিক অবস্থা থেকে আরেকটি সঠিক অবস্থায় নেয় (ACID-এর C). তাহলে যেকোনো serial schedule-ও সঠিক — একটির পর একটি সঠিক ধাপ। সুতরাং কোনো মিশ্র schedule যদি কোনো একটি serial schedule-এর সমতুল্য হয়, সেটিও সঠিক। একে বলে serializability, আর এটিই একসাথে চলা transaction-এর সঠিকতার প্রচলিত মাপকাঠি।

দুটি সূক্ষ্ম কথা

এক: “কোনো এক” serial ক্রম — নির্দিষ্ট কোনোটি নয়। n টি transaction-এর n! টি serial ক্রম আছে, আর DBMS কথা দেয় না কোনটির সমান হবে। T1 আগে না T2 আগে, দুটিই গ্রহণযোগ্য। দুই: দুটি transaction-এ চারটি করে অপারেশন হলে মোট schedule ৮! / (৪! · ৪!) = ৭০টি, তার মাত্র ২টি serial. বাকি ৬৮টির কোনগুলো নিরাপদ, তা বাছাই করাই এই পর্বের কাজ।

“সমতুল্য” মানে কী? সবচেয়ে সরল চিন্তা: শেষ অবস্থা মিললেই হলো (result equivalence). কিন্তু সেটি সংখ্যার কাকতালীয় মিলের ওপর নির্ভর করে — অন্য প্রাথমিক মানে মিল ভেঙে যেতে পারে, আর DBMS transaction-এর ভেতরের হিসাব জানেই না। তাই দরকার এমন সংজ্ঞা, যা শুধু read আর write-এর ক্রম দেখে বিচার করে, সব প্রাথমিক মানের জন্য। এমন দুটি সংজ্ঞা আছে: conflict আর view.

৩ · Conflict — কোন দুটি অপারেশনের ক্রম গুরুত্বপূর্ণ

দুটি পাশাপাশি অপারেশন অদলবদল করলে কি ফল বদলায়? দুটি আলাদা item হলে কখনো না — A আর B-র কাজ একে অপরকে ছোঁয় না। একই item হলে নির্ভর করে read না write:

জোড়া (একই item)Conflict?কেন
R1(A), R2(A)নাদুজনেই একই মান পড়ে, ক্রম যা-ই হোক
R1(A), W2(A) (RW)হ্যাঁআগে পড়লে পুরোনো মান, পরে পড়লে T2-এর মান
W1(A), R2(A) (WR)হ্যাঁT2 কোন মান পড়বে, তা বদলে যায়
W1(A), W2(A) (WW)হ্যাঁযে পরে লেখে, তার মানই টেকে

সংজ্ঞা: দুটি অপারেশন conflicting, যদি (১) তারা আলাদা transaction-এর, (২) একই item-এর, আর (৩) অন্তত একটি write. একই transaction-এর দুটি অপারেশন কখনো অদলবদল হয় না — নিজের ক্রম অটুট রাখা schedule-এর সংজ্ঞাতেই আছে।

এখান থেকে প্রথম সমতুল্যতা। একটি schedule থেকে পাশাপাশি দুটি non-conflicting অপারেশন বারবার অদলবদল করে আরেকটি schedule পাওয়া গেলে দুটি conflict equivalent. আর কোনো serial schedule-এর conflict equivalent হলে schedule-টি conflict serializable. নিচে S আর S′ দুটিকেই অদলবদলে serial বানানোর চেষ্টা:

পাশাপাশি দুটি অপারেশনের অদলবদলসবুজ = এই ধাপে সরানো জোড়া

            
হিসাব
অদলবদল হয়েছে
এই ধাপের জোড়া
conflict?
অবস্থা
  1. শুরু। S-এ T2 A নিয়ে কাজ করে T1-এর A-র কাজের পরে, কিন্তু T1-এর B-র কাজের আগে। লক্ষ্য: T1-এর R1(B) আর W1(B)-কে T2-এর A-র অপারেশনের ওপরে তোলা।
  2. প্রথম অদলবদল। W2(A) আর R1(B) — আলাদা item, তাই conflict নেই; জায়গা বদল নিরাপদ। R1(B) এক ধাপ ওপরে উঠল।
  3. দ্বিতীয়। R2(A) আর R1(B) — আবার আলাদা item. R1(B) এখন T1-এর A-র কাজের ঠিক পরে।
  4. তৃতীয়। W2(A) আর W1(B) — দুটিই write, কিন্তু আলাদা item; conflict শুধু একই item-এ।
  5. চতুর্থ। R2(A) আর W1(B) — আলাদা item. এখন T1-এর চারটি অপারেশন একসাথে ওপরে, T2-এর চারটি নিচে।
  6. ফল। চারটি নিরাপদ অদলবদলে S হয়ে গেছে serial T1, T2. প্রতিটি অদলবদলে ফল অপরিবর্তিত, তাই S-এর ফল যেকোনো প্রাথমিক মানে T1, T2-এর ফলের সমান — শুধু এই ১,০০০ আর ২,০০০-এ নয়।
  7. S′ আটকে যায়। T1-কে আগে আনতে W1(A)-কে R2(A)-র ওপরে তুলতে হবে — একই item, একটি write: নিষেধ। T2-কে আগে আনতে W2(A)-কে R1(A)-র ওপরে — সেটিও conflict. দুই পথই বন্ধ, তাই S′ conflict serializable নয়।

এই হাতের খেলা বড় schedule-এ চলবে না, তবে এর সার কথাটি সহজ: অদলবদলে conflicting জোড়ার ক্রম কখনো বদলায় না. তাই আসল প্রশ্ন — সব conflicting জোড়া কোন দিকে মুখ করে আছে। নিচের এডিটরে schedule রাখা একটি টেবিলে: ops(pos, tx, op, item) — pos সময়ের ক্রম, tx transaction-এর নম্বর, op 'R' বা 'W', item ডেটার নাম। একটি self join (পর্ব ২০) সব conflicting জোড়া বের করে। ডেটা S′-এর:

CREATE TABLE ops (pos INTEGER PRIMARY KEY, tx INTEGER, op TEXT, item TEXT);
INSERT INTO ops VALUES (1,1,'R','A'), (2,2,'R','A'), (3,1,'W','A'), (4,2,'W','A'),
                       (5,1,'R','B'), (6,1,'W','B'), (7,2,'R','B'), (8,2,'W','B');
SELECT a.op || a.tx || '(' || a.item || ')' AS first,
       b.op || b.tx || '(' || b.item || ')' AS second,
       a.op || b.op AS kind,
       'T' || a.tx || '→T' || b.tx AS edge
FROM ops a JOIN ops b
  ON a.item = b.item AND a.tx <> b.tx AND a.pos < b.pos
WHERE a.op = 'W' OR b.op = 'W'
ORDER BY a.pos, b.pos;

ছয়টি সারি: R1(A) | W2(A) | RW | T1→T2, R2(A) | W1(A) | RW | T2→T1, W1(A) | W2(A) | WW | T1→T2, R1(B) | W2(B) | RW | T1→T2, W1(B) | R2(B) | WR | T1→T2, W1(B) | W2(B) | WW | T1→T2. পাঁচটি জোড়া বলছে “T1 আগে”, একটি — R2(A) আর W1(A) — বলছে “T2 আগে”। দুটি একসাথে কোনো serial ক্রমে সম্ভব নয়। শর্ত তিনটি সরাসরি ON আর WHERE-এ: একই item, আলাদা transaction, অন্তত একটি W; আর a.pos < b.pos জোড়ার দিক ঠিক করে। ২ নম্বর সারি বদলে (2,1,'W','A') আর ৩ নম্বর (3,2,'R','A') করলে S পাবেন — তখন সব edge T1→T2.

৪ · Precedence graph আর মূল উপপাদ্য

ওপরের edge কলামটিই পরের ধারণার বীজ। একটি schedule S-এর precedence graph (serialization graph নামেও পরিচিত) একটি directed graph:

  • Node: S-এর প্রতিটি transaction একটি।
  • Edge Ti → Tj: যদি Ti-এর কোনো অপারেশন Tj-এর কোনো অপারেশনের সাথে conflict করে, আর Ti-এরটি S-এ আগে আসে। অর্থ: “যেকোনো সমতুল্য serial ক্রমে Ti-কে Tj-এর আগে থাকতে হবে।”

এবার তিনটি transaction-এর একটি schedule, অপারেশন ধরে ধরে graph গড়ে তোলা:

S₃: তিনটি transaction, দুটি itemসবুজ = এই ধাপে তুলনা করা অপারেশন

            
Graph-এর খাতা
দেখা হয়েছে
edge
cycle?
  1. শুরু। তিনটি node — T1, T2, T3. প্রতিটি অপারেশনে গিয়ে দেখব, আগের কোন অপারেশনের সাথে তার conflict.
  2. প্রথম তিনটি। R2(A), R1(B), W2(A): W2(A)-এর আগে A-তে শুধু T2-এর নিজের R2(A); R1(B) অন্য item. কোনো edge নেই।
  3. R3(A). আগে W2(A) — আলাদা transaction, একই item, একটি write. প্রথম edge: T2 → T3. (R2(A) আর R3(A) দুটিই read, তাই সেটি গোনা হয় না।)
  4. W1(B) আর W3(A). W1(B)-এর আগে B-তে শুধু T1-এর নিজের R1(B). W3(A)-এর আগে R2(A) আর W2(A) — দুটিই T2 → T3 দেয়, যা আগেই আছে। একটি edge একবারই আঁকা হয়।
  5. R2(B). আগে W1(B) — WR conflict. নতুন edge: T1 → T2.
  6. W3(B). আগে R1(B), W1(B) — দুটিই T1 → T3; আর R2(B) — T2 → T3, পুরোনো। graph সম্পূর্ণ: তিনটি edge.
  7. সিদ্ধান্ত। কোনো cycle নেই। T1-এর দিকে কোনো edge আসে না, তাই সে প্রথম; তারপর T2, শেষে T3. S₃ conflict equivalent to serial T1 → T2 → T3.
উপপাদ্য

একটি schedule S conflict serializable যদি এবং কেবল যদি তার precedence graph-এ কোনো cycle না থাকে। আর তখন graph-এর যেকোনো topological ক্রমই একটি সমতুল্য serial ক্রম।

প্রমাণ (⇒, cycle থাকলে serializable নয়): ধরুন S একটি serial schedule Ss-এর conflict equivalent. অদলবদলে conflicting জোড়ার ক্রম বদলায় না, তাই S আর Ss-এর precedence graph হুবহু এক। serial schedule-এ edge Ti → Tj মানে Ti পুরোটা Tj-এর আগে শেষ হয়েছে। এখন graph-এ যদি cycle T1 → T2 → … → Tk → T1 থাকে, তাহলে Ss-এ T1 আছে T2-এর আগে, … , Tk-এর আগে, আবার Tk আছে T1-এর আগে — অর্থাৎ T1 নিজের আগে। অসম্ভব। তাই cycle থাকলে S conflict serializable নয়।

প্রমাণ (⇐, cycle না থাকলে serializable): cycle-হীন directed graph-এর (DAG) সবসময় একটি topological ক্রম থাকে — node-গুলোর এমন সারি, যেখানে প্রতিটি edge সামনের দিকে যায়। সেই ক্রমে transaction-গুলো সাজিয়ে serial schedule Ss বানান। S-এর যেকোনো conflicting জোড়া (Ti-এর p আগে, Tj-এর q পরে) একটি edge Ti → Tj দিয়েছে, তাই Ss-এও Ti আগে, অর্থাৎ p আগে। তাহলে S আর Ss-এ সব conflicting জোড়ার ক্রম এক। এবার S-কে bubble sort-এর মতো Ss-এর দিকে নিন: যতক্ষণ Ss-এর তুলনায় উল্টো ক্রমের কোনো পাশাপাশি জোড়া আছে, সেটি অদলবদল করুন। সেই জোড়া একই transaction-এর হতে পারে না (নিজের ক্রম দুটিতেই এক), conflicting-ও হতে পারে না (তাদের ক্রম দুটিতেই এক) — তাই অদলবদল বৈধ, আর প্রতিবার উল্টো জোড়ার সংখ্যা একটি কমে। শেষে S = Ss. ∎

যাচাইয়ের খরচও স্পষ্ট: n টি অপারেশনের schedule-এ জোড়া তুলনা O(n²), আর t টি transaction আর e টি edge-এর graph-এ cycle খোঁজা O(t + e). এবার SQL-এ একই কাজ। নিচের ডেটা একটি নতুন schedule: R1(A) R2(B) R3(C) W1(B) W2(C) W3(A). প্রথম CTE edge বানায়; walk নামের recursive CTE (পর্ব ২৫) প্রতিটি edge থেকে হাঁটা শুরু করে, আর যে হাঁটা শুরুর node-এ ফিরে আসে, সেটি cycle. হাঁটার দৈর্ঘ্য transaction-এর সংখ্যায় সীমিত, যাতে recursion থামে — পর্ব ২৫-এর cycle detection-এর কৌশল।

INSERT INTO ops VALUES (1,1,'R','A'), (2,2,'R','B'), (3,3,'R','C'),
                       (4,1,'W','B'), (5,2,'W','C'), (6,3,'W','A');
WITH RECURSIVE
edges(src, dst) AS (
  SELECT DISTINCT a.tx, b.tx FROM ops a JOIN ops b
    ON a.item = b.item AND a.tx <> b.tx AND a.pos < b.pos
  WHERE a.op = 'W' OR b.op = 'W'),
walk(start, cur, len, path) AS (
  SELECT src, dst, 1, 'T' || src || '→T' || dst FROM edges
  UNION ALL
  SELECT w.start, e.dst, w.len + 1, w.path || '→T' || e.dst
  FROM walk w JOIN edges e ON e.src = w.cur
  WHERE w.cur <> w.start AND w.len < (SELECT COUNT(DISTINCT tx) FROM ops))
SELECT path AS cycle FROM walk WHERE cur = start;

তিনটি সারি: T1→T3→T2→T1, T2→T1→T3→T2, T3→T2→T1→T3 — একই cycle, তিনটি node থেকে শুরু করে দেখা। edge তিনটি: R1(A) আর W3(A) থেকে T1 → T3, R2(B) আর W1(B) থেকে T2 → T1, R3(C) আর W2(C) থেকে T3 → T2. প্রতিটি transaction অন্য একজনের পড়া item-এ পরে লিখেছে, ফলে একটি গোল শিকল — schedule-টি conflict serializable নয়। একটি সংখ্যা বদলে দেখুন: ৬ নম্বর সারিতে 'A'-এর জায়গায় 'D' লিখলে T1 → T3 edge আর থাকে না, cycle ভাঙে, আর ফল খালি — কোনো সারি নেই মানে কোনো cycle নেই।

Graph আঁকার চারটি সাধারণ ভুল। (১) দুটি read-কে edge ধরা — R-R কখনো conflict নয়। (২) একই transaction-এর দুটি অপারেশন থেকে edge আঁকা — নিজের দিকে edge হয় না। (৩) শুধু পাশাপাশি অপারেশন দেখা — conflict-এর জন্য দুটি অপারেশন পাশাপাশি হতে হয় না; ১ নম্বর আর ৮ নম্বরও জোড়া হতে পারে। (৪) দিক উল্টো আঁকা — edge সবসময় schedule-এ আগে আসা অপারেশনের transaction থেকে পরেরটির দিকে। হাতে করার নিরাপদ কৌশল: প্রতিটি item-এর অপারেশনগুলো আলাদা লাইনে ক্রম ধরে লিখুন (যেমন A: R2, W2, R3, W3), তারপর প্রতিটি write-এর সাথে তার আগের ও পরের অন্য transaction-এর অপারেশন মেলান।

৫ · সমতুল্য serial ক্রম — topological sort

Graph-এ cycle না থাকলে উত্তরের দ্বিতীয় অংশ: কোন serial ক্রম? সেটি topological sort — DSA কোর্সের topological sort পর্বে যে অ্যালগরিদম বিস্তারিত দেখেছেন। Kahn-এর পদ্ধতিতে: যে node-এর দিকে কোনো edge আসে না, তাকে সারিতে বসান, তার edge-গুলো মুছে দিন, আবার একই কাজ। একাধিক node একসাথে প্রস্তুত থাকলে যেকোনোটি আগে নেওয়া যায় — তাই সমতুল্য serial ক্রম একাধিক হতে পারে।

SQL-এ একটি সহজ বিকল্প: প্রতিটি transaction-এর level = কোনো উৎস থেকে তার কাছে আসার দীর্ঘতম পথের দৈর্ঘ্য। edge Ti → Tj থাকলে level(Tj) ≥ level(Ti) + ১, তাই level অনুযায়ী সাজালে সব edge সামনের দিকে যায় — একটি বৈধ topological ক্রম। একই level-এর transaction-গুলো যেকোনো ক্রমে বসতে পারে। এই এডিটরে চারটি transaction-এর schedule: R2(A) W2(A) W2(B) R1(A) R4(B) W1(C) W4(D) R3(C) R3(D).

INSERT INTO ops VALUES (1,2,'R','A'), (2,2,'W','A'), (3,2,'W','B'), (4,1,'R','A'),
  (5,4,'R','B'), (6,1,'W','C'), (7,4,'W','D'), (8,3,'R','C'), (9,3,'R','D');
WITH RECURSIVE
edges(src, dst) AS (
  SELECT DISTINCT a.tx, b.tx FROM ops a JOIN ops b
    ON a.item = b.item AND a.tx <> b.tx AND a.pos < b.pos
  WHERE a.op = 'W' OR b.op = 'W'),
lvl(tx, d) AS (
  SELECT DISTINCT tx, 0 FROM ops
  UNION ALL
  SELECT e.dst, l.d + 1 FROM lvl l JOIN edges e ON e.src = l.tx
  WHERE l.d < (SELECT COUNT(DISTINCT tx) FROM ops))
SELECT 'T' || tx AS tx, MAX(d) AS level FROM lvl
GROUP BY tx ORDER BY level, tx;

ফল: T2 | 0, T1 | 1, T4 | 1, T3 | 2. edge চারটি: T2 → T1 (W2(A), R1(A)), T2 → T4 (W2(B), R4(B)), T1 → T3 (W1(C), R3(C)), T4 → T3 (W4(D), R3(D)). তাই সমতুল্য serial ক্রম দুটি: T2, T1, T4, T3 আর T2, T4, T1, T3 — T1 আর T4 একে অপরের কোনো item ছোঁয় না, তাদের মধ্যে কোনো edge নেই। সতর্কতা: cycle থাকলে level অসীমভাবে বাড়ত; l.d < … শর্তটি তা থামায়। কোনো level transaction-এর সংখ্যার সমান হলে (এখানে ৪) বুঝবেন cycle আছে — প্রথম এডিটরের S′-এর ডেটা এখানে দিলে দুটি transaction-ই level 2 পায়, অথচ মাত্র দুটি transaction.

এবার নিজের যেকোনো schedule পরীক্ষা করুন। নিচের ছোট JavaScript চেকারটি পুরো কাজ একবারে করে: schedule-এর লেখা পড়ে, conflicting জোড়া থেকে edge বানায় (প্রতিটি edge প্রথম কোন জোড়া থেকে এলো তা দেখায়), তারপর Kahn-এর topological sort চালায়। সব node সারিতে বসলে serializable, নইলে cycle. প্রথম লাইনের লেখাটি বদলে চালান — C1 বা A2-এর মতো commit/abort লিখলে চেকার সেগুলো উপেক্ষা করে।

const schedule = "R1(A) R2(A) W1(A) W2(A) R1(B) W1(B) R2(B) W2(B)";

const ops = schedule.trim().split(/\s+/).filter(s => !/^[CA]\d+$/.test(s)).map(s => {
  const m = s.match(/^([RW])(\d+)\((\w+)\)$/);
  if (!m) throw new Error("এই অংশটি বোঝা গেল না: " + s);
  return { op: m[1], tx: "T" + m[2], item: m[3], text: s };
});
const txs = [...new Set(ops.map(o => o.tx))];
const out = new Map(txs.map(t => [t, new Set()]));     // precedence graph
ops.forEach((a, i) => ops.slice(i + 1).forEach(b => {
  if (a.item === b.item && a.tx !== b.tx && (a.op === "W" || b.op === "W") && !out.get(a.tx).has(b.tx)) {
    out.get(a.tx).add(b.tx);
    console.log(`${a.text} আগে, ${b.text} পরে → edge ${a.tx}→${b.tx}`);
  }
}));
// Kahn-এর topological sort: যার দিকে কোনো edge আসে না, সে আগে
const indeg = new Map(txs.map(t => [t, 0]));
out.forEach(set => set.forEach(t => indeg.set(t, indeg.get(t) + 1)));
const ready = txs.filter(t => indeg.get(t) === 0), order = [];
while (ready.length) {
  const t = ready.shift();
  order.push(t);
  out.get(t).forEach(u => { indeg.set(u, indeg.get(u) - 1); if (indeg.get(u) === 0) ready.push(u); });
}
if (order.length === txs.length) console.log("Conflict serializable ✓  সমতুল্য serial ক্রম: " + order.join(" → "));
else console.log("Conflict serializable নয় ✗  cycle-এ (বা তার পরে) আটকে আছে: " + txs.filter(t => !order.includes(t)).join(", "));

S′-এর জন্য তিনটি লাইন: R1(A) আগে, W2(A) পরে → edge T1→T2, R2(A) আগে, W1(A) পরে → edge T2→T1, আর Conflict serializable নয় ✗ … T1, T2. edge-এর পুনরাবৃত্তি ছাপা হয় না, তাই SQL-এর ছয়টি জোড়ার বদলে দুটি লাইন। চার-transaction-এর schedule দিলে চেকার বলে T2 → T1 → T4 → T3 — দুটি বৈধ ক্রমের একটি; Kahn যে প্রস্তুত node আগে পায়, তাকে আগে নেয়। খেয়াল করুন: “cycle-এ বা তার পরে” — Kahn শুধু বলে কোন node-গুলো সারিতে বসতে পারেনি; cycle-এর পরের node-ও তাদের মধ্যে থাকে।

৬ · View serializability — blind write-এর জগৎ

Conflict serializability নিরাপদ, কিন্তু কিছু নির্দোষ schedule-কেও বাদ দেয়। এই schedule দেখুন — T1 A পড়ে আর লেখে, T2 আর T3 না পড়েই A-তে লেখে:

#T1T2T3
১R1(A)
২W2(A)
৩W1(A)
৪W3(A)

Precedence graph: R1(A) → W2(A) থেকে T1 → T2, আর W2(A) → W1(A) থেকে T2 → T1 — cycle, তাই conflict serializable নয়। অথচ ভাবুন: A-র শেষ মান সবসময় T3-এর লেখা, আর T1 যা পড়ল তা প্রাথমিক মান। serial T1, T2, T3-এও ঠিক তা-ই। কেউ “ভুল” কিছু দেখেনি। T2 আর T3-এর write-কে বলে blind write — নিজে না পড়ে লেখা, যেমন “ঠিকানা বদলে নতুন ঠিকানা লেখো”, পুরোনোটি না দেখেই।

এই উদারতা ধরতে view equivalence. একই transaction-গুলোর দুটি schedule S আর S′ view equivalent, যদি প্রতিটি item X-এর জন্য তিনটি শর্ত মানে:

  1. প্রাথমিক read: S-এ কোনো Ti X-এর প্রাথমিক মান পড়লে S′-এও Ti প্রাথমিক মান পড়ে।
  2. Read-from: S-এ Ti যদি Tj-এর লেখা X পড়ে (অর্থাৎ Ri(X)-এর ঠিক আগের শেষ write-টি Wj(X)), তাহলে S′-এও তা-ই।
  3. শেষ write: S-এ X-এর শেষ write যে transaction করে, S′-এও সে-ই করে।

প্রথম দুটি নিশ্চিত করে প্রতিটি transaction একই মান দেখে, তাই একই হিসাব করে; তৃতীয়টি নিশ্চিত করে ডেটাবেসের শেষ অবস্থা এক। কোনো serial schedule-এর view equivalent হলে S view serializable. ওপরের S₄ আর serial T1, T2, T3 মিলিয়ে দেখুন: R1(A) দুটিতেই প্রাথমিক মান পড়ে; আর কোনো read নেই; A-র শেষ write দুটিতেই W3(A). তিনটি শর্ত পূরণ — S₄ view serializable, কিন্তু conflict serializable নয়। অন্য serial ক্রম কেন চলবে না, তা-ও দেখুন: T2 যদি T1-এর আগে থাকে, R1(A) পড়বে T2-এর লেখা — প্রথম শর্ত ভাঙে; T3 শেষে না থাকলে A-র শেষ write অন্য কারও — তৃতীয় শর্ত ভাঙে। তাই একমাত্র উত্তর T1, T2, T3. view-এর প্রশ্নে এভাবেই এগোন: শর্তগুলো থেকে “কে কার আগে থাকতেই হবে” বের করুন, তারপর এমন একটি serial ক্রম খুঁজুন যা সবকটি মানে।

  • প্রতিটি conflict serializable schedule view serializable: অদলবদলে conflicting জোড়ার ক্রম বজায় থাকে, তাই কে কার লেখা পড়ে আর কে শেষে লেখে — তিনটিই WR আর WW জোড়ার ক্রম দিয়ে নির্ধারিত — বদলায় না। উল্টোটা সত্য নয়, S₄ তার প্রমাণ।
  • পার্থক্য আসে শুধু blind write থেকে: কোনো schedule view serializable কিন্তু conflict serializable নয় হলে তাতে অন্তত একটি blind write আছে। blind write না থাকলে দুই ধারণা এক।
  • যাচাই কঠিন: ১৯৭৯ সালে Christos Papadimitriou প্রমাণ করেন, একটি schedule view serializable কি না তা যাচাই করা NP-complete. সাধারণ graph যথেষ্ট নয়; লাগে “polygraph” — এমন edge-জোড়া, যার যেকোনো একটি বেছে নিতে হয়, আর বাছাইয়ের সংখ্যা সূচকীয় হারে বাড়ে।

তাই বাস্তব DBMS view serializability সরাসরি যাচাই করে না। এর মূল্য তাত্ত্বিক: সঠিকতার একটি বড় সীমানা আঁকা। তবে একটি জায়গায় এর ছায়া আছে — timestamp-ভিত্তিক concurrency control-এর Thomas write rule পুরোনো blind write নিঃশব্দে ফেলে দিয়ে কিছু view-serializable schedule মেনে নেয়; পর্ব ৪৬-এ দেখবেন।

৭ · Recoverability — commit-এর ক্রমও গুরুত্বপূর্ণ

এ পর্যন্ত ধরে নিয়েছি সব transaction commit করে। বাস্তবে transaction abort করে — ব্যালান্স কম, constraint ভাঙা, deadlock, বা অ্যাপের নিজের সিদ্ধান্তে। তখন তার লেখা মুছতে হয়। সমস্যা হয় যদি অন্য কেউ ইতিমধ্যে সেই লেখা পড়ে ফেলে — commit না হওয়া ডেটা পড়াকে বলে dirty read. এখান থেকে তিনটি ক্রমশ কড়া শর্ত:

Recoverable

Recoverable: Tj যদি Ti-এর লেখা পড়ে, তাহলে Ti-এর commit Tj-এর commit-এর আগে। কেউ এমন ডেটার ওপর ভর করে commit করে না, যা পরে মুছে যেতে পারে।

Cascadeless (ACA)

Cascadeless বা avoids cascading aborts: কেউ শুধু commit হয়ে যাওয়া ডেটা পড়ে। Tj যদি Ti-এর লেখা X পড়ে, তাহলে Ti-এর commit Rj(X)-এর আগেই।

Strict

Strict: Ti X-এ লিখলে, Ti commit বা abort না করা পর্যন্ত অন্য কেউ X পড়েও না, লেখেও না। abort মানে শুধু X-এর পুরোনো মান (before-image) ফেরত বসানো।

শর্তগুলো না মানলে কী হয়, ধাপে ধাপে দেখুন। T1 A লেখে, T2 সেটি পড়ে B লেখে, T3 সেই B পড়ে; T4 আলাদা item নিয়ে কাজ করে। তারপর T1 abort করে:

চারটি transaction, একটি abortসবুজ = এই ধাপে জড়িত অপারেশন

            
কার কী দশা
T1
T2
T3
T4
  1. শুরু। T1 A-তে লিখেছে, এখনো commit করেনি। বাকিরা নিজেদের কাজ চালাচ্ছে, T4 commit করেছে।
  2. Read-from শিকল। R2(A) পড়ে T1-এর অসমাপ্ত লেখা, R3(B) পড়ে T2-এর — দুটিই dirty read. T4 শুধু D নিয়ে কাজ করে, কারও লেখা পড়েনি।
  3. T1 abort. DBMS W1(A) মুছে A-র পুরোনো মান ফেরত বসায়। কিন্তু T1-এর লেখা এর মধ্যে ছড়িয়ে গেছে।
  4. T2-কেও abort. T2 যে A দেখে হিসাব করেছে, সেই মান কখনো “সত্যি” ছিল না। তার W2(B)-ও ভুল ভিত্তিতে — মুছতে হবে।
  5. T3-ও abort. T3 পড়েছিল T2-এর B. একটি abort থেকে তিনটি — cascading abort. T4 টিকে থাকে, কারণ সে শিকলে নেই।
  6. আরও খারাপ: non-recoverable. A1-এর আগে T2 commit করে ফেললে তাকে আর ফেরানো যায় না — commit-এর প্রতিশ্রুতি (ACID-এর D) ভাঙতে হবে, নয়তো ভুল ডেটা স্থায়ী থাকবে। recoverable শর্ত ঠিক এটিই নিষেধ করে: C1-এর আগে C2 নয়।
  7. Cascadeless প্রতিকার। R2(A) অপেক্ষা করে T1 commit বা abort করা পর্যন্ত। T1 abort হলে T2 পুরোনো সঠিক A পড়ে — শুধু T1-এর কাজ নষ্ট হয়, শিকল তৈরিই হয় না।

Cascadeless হলেও একটি ফাঁক থাকে। W1(A) W2(A) A1: কেউ dirty read করেনি, কিন্তু T1 abort হলে A-তে T1-এর before-image বসালে T2-এর লেখাও মুছে যায়। strict শর্ত এই ফাঁক বন্ধ করে — অসমাপ্ত লেখার ওপর লেখাও নিষেধ — ফলে recovery-র নিয়ম সরল থাকে: “abort মানে নিজের লেখা item-এ before-image বসাও”। পর্ব ৪৮-এর লগ-ভিত্তিক recovery এর ওপরই দাঁড়িয়ে।

Schedule-এর দুই পরিবার

Serializability (left) and recoverability (right) are separate properties; serial schedules sit in the overlap

সব schedule view serializable conflict serializable cycle-হীন precedence graph recoverable cascadeless (ACA) strict serial commit-এর ক্রম আর dirty read
দুটি আলাদা প্রশ্ন: “ফল কি কোনো serial ক্রমের সমান?” আর “abort হলে কি নিরাপদে ফেরানো যায়?” একটি schedule conflict serializable হয়েও non-recoverable হতে পারে, আবার strict হয়েও serializable না হতে পারে। শেষ write-এর পরে commit করা serial schedule দুই পরিবারেরই সবচেয়ে ভেতরে।

৮ · বাস্তব DBMS কীভাবে নিশ্চিত করে

কোনো DBMS আগে schedule বানিয়ে পরে graph পরীক্ষা করে না — ততক্ষণে অপারেশনগুলো ঘটে গেছে। বদলে সে এমন concurrency control প্রটোকল মেনে চলে, যা কেবল নিরাপদ schedule-ই তৈরি হতে দেয়। সবচেয়ে প্রচলিতটি two-phase locking (2PL): প্রতিটি transaction পড়ার আগে shared lock, লেখার আগে exclusive lock নেয়, আর নিয়ম একটাই — একবার কোনো lock ছাড়লে আর নতুন lock নেওয়া যাবে না. ফলে দুটি পর্ব: growing (শুধু নেওয়া) আর shrinking (শুধু ছাড়া)।

কেন 2PL conflict serializable schedule দেয় (প্রমাণের রূপরেখা): প্রতিটি transaction-এর lock point ধরুন — যে মুহূর্তে সে শেষ lock-টি পায়। edge Ti → Tj মানে কোনো item X-এ Ti-এর অপারেশন আগে আর Tj-এর conflicting অপারেশন পরে; দুটির lock একসাথে থাকতে পারে না (অন্তত একটি exclusive), তাই Ti X-এর lock ছেড়েছে Tj তা পাওয়ার আগে। Ti কোনো lock ছাড়ে তার lock point-এর পরে, আর Tj X-এর lock পায় তার lock point-এর আগে বা ঠিক তখনই। তাই lockpoint(Ti) < lockpoint(Tj). সব edge lock point বাড়ার দিকে যায়, ফলে cycle অসম্ভব; সমতুল্য serial ক্রম হলো lock point-এর ক্রম।

অংশ ১-এর খারাপ S′ 2PL-এ কী হতো? R1(A) আর R2(A) দুজনেই A-র shared lock পায় — shared lock একসাথে চলে। এবার W1(A)-এর জন্য T1-এর লাগে exclusive lock, কিন্তু T2-এর shared lock আছে: T1 অপেক্ষা করে। W2(A)-এর জন্য T2-ও exclusive চায়, T1-এর shared lock-এর কারণে অপেক্ষা করে। দুজনেই দুজনের অপেক্ষায় — deadlock. DBMS একজনকে abort করে, ধরুন T2; T1 এগিয়ে যায়, T2 পরে নতুন করে চলে নতুন মান পড়ে। lost update ঘটতেই পারল না — দাম একটি abort আর আবার চালানো। cycle-ওয়ালা schedule 2PL-এ এভাবেই আটকে যায়: হয় অপেক্ষায়, নয়তো deadlock-এ।

সাধারণ 2PL কিন্তু recoverability দেয় না: shrinking পর্বে exclusive lock ছেড়ে দিলে অন্যরা commit-এর আগেই সেই লেখা পড়তে পারে। তাই বাস্তবে চলে strict 2PL: exclusive lock ধরে রাখা হয় commit বা abort পর্যন্ত (rigorous 2PL-এ সব lock). তখন কেউ অসমাপ্ত লেখা পড়তেও পারে না, তার ওপর লিখতেও পারে না — schedule strict, তাই cascadeless আর recoverable-ও, আবার conflict serializable. দাম আছে: lock-এর অপেক্ষা আর deadlock — পর্ব ৪৬-এর বিষয়।

  • SQLite: একসাথে একজনই লেখে (ডেটাবেস-স্তরের lock), তাই লেখার transaction-গুলো আসলেই একটির পর একটি; স্বাভাবিক আচরণ SERIALIZABLE.
  • MySQL/InnoDB: স্বাভাবিক isolation level REPEATABLE READ. SERIALIZABLE-এ (autocommit বন্ধ থাকলে) সাধারণ SELECT-ও shared lock নিয়ে পড়ে — 2PL-এর ধাঁচ।
  • PostgreSQL: স্বাভাবিক READ COMMITTED. ৯.১ সংস্করণ (২০১১) থেকে SERIALIZABLE মানে serializable snapshot isolation (SSI): lock-এ আটকে না রেখে, নিরাপদ নয় এমন নির্ভরতার নকশা দেখলে একটি transaction-কে বাতিল করে।
  • Oracle: স্বাভাবিক READ COMMITTED; তার “SERIALIZABLE” আসলে snapshot isolation, যা কিছু non-serializable ফল (write skew) হতে দেয় — পর্ব ৪৭-এ বিস্তারিত।

শেষ দুটির একটি ব্যবহারিক পরিণতি: SERIALIZABLE-এ অ্যাপকে ব্যর্থ transaction আবার চালানোর জন্য প্রস্তুত থাকতে হয়। তুলনার জন্য (এখানে চালানো যায় না):

BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT;
-- বিপজ্জনক নির্ভরতা ধরা পড়লে কোনো একটি statement বা COMMIT ব্যর্থ হয়:
-- ERROR: could not serialize access due to read/write dependencies
--        among transactions   (SQLSTATE 40001)
-- অ্যাপের কাজ: ROLLBACK, তারপর পুরো transaction আবার চালানো

আরও গভীরে: 2PL যত schedule তৈরি করতে পারে, তা conflict serializable schedule-এর একটি প্রকৃত উপসেট — কিছু cycle-হীন schedule কোনো 2PL lock-বিন্যাসেই ঘটতে পারে না। SSI-এর ভিত্তি Fekete ও সহকর্মীদের একটি ফল: snapshot isolation-এ প্রতিটি non-serializable ঘটনার dependency graph-এ পরপর দুটি read-write নির্ভরতা (“dangerous structure”) থাকে; Cahill, Röhm আর Fekete-র ২০০৮ সালের প্রবন্ধ এই নকশা ধরে transaction বাতিল করার কৌশল দেয়, যা PostgreSQL গ্রহণ করে। সতর্কতা হিসেবে এটি কিছু নির্দোষ transaction-ও বাতিল করে (false positive), কিন্তু কখনো non-serializable ফল commit হতে দেয় না।

৯ · পরীক্ষার ধাঁচের সমস্যা — পূর্ণ সমাধান

বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই অধ্যায়ের প্রশ্ন প্রায় সবসময় একই কাঠামোর: schedule দেওয়া, graph আঁকুন, serializable কি না বলুন, ক্রম দিন, recoverability শ্রেণি বলুন। পাঁচটি নমুনা:

সমস্যা ১ · R1(A) R2(B) W2(A) W1(B)

Graph: R1(A), W2(A) → T1 → T2; R2(B), W1(B) → T2 → T1. দুটি transaction-এর cycle — conflict serializable নয়. View? W2(A) আর W1(B) দুটিই blind write, তাই প্রশ্নটি খোলা; serial ক্রম দুটি পরীক্ষা করুন। T1, T2-তে R2(B) পড়ত T1-এর লেখা B, অথচ S-এ পড়ে প্রাথমিক মান — অমিল। T2, T1-এ R1(A) পড়ত T2-এর লেখা A — অমিল। View serializable-ও নয়. শিক্ষা: blind write থাকা view serializability-র প্রয়োজনীয় শর্ত, যথেষ্ট নয়।

সমস্যা ২ · R1(A) W1(A) R2(B) W2(B) R3(A) R3(B)

Graph: W1(A), R3(A) → T1 → T3; W2(B), R3(B) → T2 → T3. T1 আর T2-এর মধ্যে কোনো conflict নেই (আলাদা item). cycle নেই — conflict serializable. topological ক্রম দুটি: T1, T2, T3 এবং T2, T1, T3. দুটিই সঠিক উত্তর; একটি দিলে যথেষ্ট, দুটি দিলে আরও ভালো।

সমস্যা ৩ · শ্রেণিবিভাগ

Scheduleশ্রেণিকারণ
W1(A) R2(A) C2 C1non-recoverableT2 পড়ে T1-এর লেখা, কিন্তু commit করে T1-এর আগে
W1(A) R2(A) C1 C2recoverable, ACA নয়commit-এর ক্রম ঠিক, কিন্তু R2(A) dirty read
W1(A) W2(A) C1 C2ACA, strict নয়কোনো read নেই; কিন্তু T1 commit-এর আগে T2 A-তে লেখে
W1(A) C1 R2(A) W2(A) C2strictT1-এর লেখা ছোঁয়া হয় শুধু C1-এর পরে

চারটিই conflict serializable (একমাত্র edge T1 → T2). serializability আর recoverability যে আলাদা মাপকাঠি, প্রথম সারিটিই তার প্রমাণ।

সমস্যা ৪ · R1(A) W1(A) R2(A) W2(A) C2 R1(B) W1(B) C1

Graph: A-তে তিনটি conflict (RW, WR, WW; R1(A) আর R2(A) দুটিই read), সবকটি T1 → T2; B শুধু T1-এর। Conflict serializable, ক্রম T1, T2. Recoverable? R2(A) পড়ে W1(A)-এর লেখা, আর C2 আসে C1-এর আগে — non-recoverable. W1(B)-এর আগে কোনো সমস্যা দেখা দিয়ে T1 abort করলে T2-এর commit করা ফল ভুল ভিত্তিতে থেকে যাবে। strict 2PL-এ এটি ঘটতেই পারত না: T1-এর A-র exclusive lock C1 পর্যন্ত থাকত, R2(A) অপেক্ষা করত।

সমস্যা ৫ · একটি সারি সরালে শ্রেণি বদলায়

#T1T2T3
১R1(A)
২W1(A)
৩R2(B)
৪W2(B)
৫C1
৬R3(A)
৭R3(B)
৮W3(B)
৯C2
১০C3

Graph: W1(A), R3(A) → T1 → T3; B-তে T2-এর দুটি অপারেশন T3-এর দুটির আগে → T2 → T3. T1 আর T2 আলাদা item ছোঁয়। cycle নেই — conflict serializable, ক্রম T1, T2, T3 বা T2, T1, T3. Recoverable? T3 পড়ে A T1 থেকে (C1 আছে C3-এর আগে) আর B T2 থেকে (C2 আছে C3-এর আগে) — হ্যাঁ। Cascadeless? R3(B) আসে ৭ নম্বরে, অথচ C2 ৯ নম্বরে — T3 পড়েছে T2-এর অসমাপ্ত লেখা, তাই না. T2 abort করলে T3-কেও যেতে হতো। প্রতিকার: C2-কে ৭ নম্বরের আগে নিয়ে এলে প্রতিটি read আর প্রতিটি পরের write commit-হওয়া ডেটার ওপর হয় — schedule তখন cascadeless, এমনকি strict. serializability একই থাকে, কারণ commit-এর অবস্থান precedence graph বদলায় না। পরীক্ষায় এই পার্থক্যটি প্রায়ই ধরা হয়: graph দেখে read আর write, recoverability দেখে commit আর abort.

উত্তর লেখার কাঠামো

লিখিত পরীক্ষায় পূর্ণ নম্বর পেতে প্রতিটি উত্তরে চারটি অংশ রাখুন: (১) conflicting জোড়ার তালিকা, প্রতিটির পাশে edge; (২) graph-এর ছবি (node আর তীর); (৩) সিদ্ধান্ত — cycle থাকলে কোন node-গুলো দিয়ে, না থাকলে topological ক্রম, একাধিক হলে সবগুলো; (৪) প্রশ্নে চাইলে recoverability — প্রতিটি read-from জোড়ার পাশে writer-এর commit কোথায়। শুধু “serializable” লিখে দিলে যুক্তি দেখা যায় না, আর পরীক্ষক যুক্তিতেই নম্বর দেন।

১০ · AI-এর যুগে serializability

এই অধ্যায়ের প্রশ্নে AI প্রায়ই আত্মবিশ্বাসী কিন্তু ভুল উত্তর দেয়, কারণ ছোট একটি ক্রমের পার্থক্যেই উত্তর উল্টে যায়। আর concurrency-র কোড লেখার সময় AI প্রায়ই transaction, isolation level আর retry-র কথাই তোলে না। সাধারণ ভুল:

AI-এর ভুলফলকীভাবে ধরবেন
R-R জোড়াকেও edge ধরা, বা একই transaction-এর জোড়া গোনানেই এমন cycle “পাওয়া”প্রতিটি edge-এর জন্য কোন দুটি অপারেশন, তা লিখতে বলুন
edge-এর দিক উল্টো (পরের থেকে আগের দিকে)ভুল serial ক্রমওপরের চেকার বা SQL-এ নিজে চালান
“cycle আছে, তাই serializable নয়” — view-এর কথা ভুলেblind write-এর ক্ষেত্রে অসম্পূর্ণ উত্তরপ্রশ্নে conflict না view, স্পষ্ট করুন
Recoverable আর cascadeless গুলিয়ে ফেলাcommit ক্রম বনাম read-এর সময় মিশে যায়সংজ্ঞা লিখিয়ে প্রতিটি read-from জোড়ায় মেলান
“SERIALIZABLE দিলেই সব ঠিক” — retry ছাড়া কোড40001 ত্রুটিতে লেনদেন হারায়ব্যর্থতা পরীক্ষা করে আবার চালানোর লুপ আছে কি না দেখুন
Oracle-এর SERIALIZABLE-কে পূর্ণ serializable বলাwrite skew-এর ঝুঁকি চোখ এড়ায়DBMS-এর নিজের ডকুমেন্টেশন মেলান

AI-কে ধাপগুলো দেখাতে বাধ্য করুন — শুধু উত্তর নয়:

এই schedule-টি বিশ্লেষণ করো: R1(X) R2(Z) R1(Z) R3(X) R3(Y) W1(X) C1 W3(Y) C3 R2(Y) W2(Z) W2(Y) C2 ১. প্রতিটি conflicting জোড়া আলাদা লাইনে লেখো: কোন দুটি অপারেশন, কোন ধরন (RW/WR/WW), কোন edge. ২. Precedence graph-এর edge-তালিকা দাও, cycle আছে কি না বলো; না থাকলে সব সম্ভাব্য serial ক্রম দাও। ৩. Recoverable, cascadeless আর strict — প্রতিটির জন্য সংজ্ঞা লিখে প্রতিটি read-from জোড়ায় মিলিয়ে দেখাও। ৪. কোনো ধাপ অনুমান করবে না; নিশ্চিত না হলে বলো।

AI-এর দেওয়া উত্তর বা কোড মেনে নেওয়ার আগে — চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Conflicting operation কখন? — আলাদা transaction, একই item, অন্তত একটি write (RW, WR, WW).
  • Conflict serializability কীভাবে যাচাই করবেন? — precedence graph আঁকুন; cycle না থাকলে serializable, topological sort-ই serial ক্রম।
  • View serializable কিন্তু conflict serializable নয় — উদাহরণ? — R1(A) W2(A) W1(A) W3(A); blind write লাগে।
  • Recoverable আর cascadeless-এর পার্থক্য? — প্রথমটিতে writer আগে commit করে; দ্বিতীয়টিতে commit-এর আগে কেউ পড়েই না।
  • Strict 2PL কোন গুণ দেয়? — conflict serializable আর strict (তাই cascadeless আর recoverable) schedule.

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১সঠিকতার মাপকাঠি হিসেবে “কোনো serial ক্রমের সমান” কেন বেছে নেওয়া হলো? প্রতিটি অ্যাপের নিজের নিয়ম জেনে আরও উদার কিছু কি করা যেত না?

১৯৭৬ সালে IBM-এর System R দলের Kapali Eswaran, Jim Gray, Raymond Lorie আর Irving Traiger-এর প্রবন্ধ “The Notions of Consistency and Predicate Locks in a Database System” (Communications of the ACM) এই প্রশ্নের প্রচলিত উত্তর দাঁড় করায়: একসাথে চলা transaction-এর ফল কোনো serial ক্রমের সমান হওয়াই সঠিকতা, আর two-phase locking তা নিশ্চিত করে। একই প্রবন্ধে phantom সমস্যা আর predicate lock-এর ধারণাও আসে।

এই মাপকাঠির সবচেয়ে বড় গুণ হলো এটি অ্যাপের নিয়ম না জেনেই কাজ করে। DBMS জানে না একটি ব্যাংকের “মোট টাকা অপরিবর্তিত থাকবে” নিয়ম, বা একটি হাসপাতালের “অন্তত একজন ডাক্তার ডিউটিতে” নিয়ম। সে শুধু ধরে নেয়, প্রতিটি transaction একা চললে নিয়ম মানে। তারপর serializability প্রতিশ্রুতি দেয় — যে নিয়মই হোক, একসাথে চললেও তা মানা হবে। অর্থাৎ প্রোগ্রামারকে concurrency নিয়ে ভাবতে হয় না; প্রতিটি transaction আলাদাভাবে সঠিক লিখলেই যথেষ্ট।

উদার বিকল্প সত্যিই আছে। গবেষণায় “semantic” concurrency control এসেছে: যেমন দুটি জমা (deposit) একই অ্যাকাউন্টে যেকোনো ক্রমে হলেও ফল এক, কারণ যোগ commutative — তাই তাদের একে অপরের অপেক্ষা করানোর দরকার নেই। Patrick O'Neil-এর ১৯৮৬ সালের escrow পদ্ধতি এই ধারণায় সংখ্যার ঘর (যেমন গুদামের মজুত) একসাথে অনেককে বদলাতে দেয়, শুধু সীমা না ভাঙলে। কিন্তু এতে প্রতিটি অপারেশনের অর্থ DBMS-কে জানাতে হয়, ভুল জানালে নিঃশব্দে ভুল ডেটা।

তাই সাধারণ DBMS serializability-কে ভিত্তি রেখেছে, আর গতির জন্য দুর্বল isolation level দিয়েছে — দায়িত্বটি প্রোগ্রামারের হাতে ফিরিয়ে।

মূল উপলব্ধি: serializability-র শক্তি তার অজ্ঞতায়: অ্যাপের কোনো নিয়ম না জেনেও সব নিয়ম রক্ষা করে। আরও উদার কিছু চাইলে অ্যাপের জ্ঞান DBMS-কে দিতে হয়, আর সেটিই ভুলের নতুন উৎস।

প্র ০২View serializability conflict-এর চেয়ে বেশি schedule মেনে নেয়, অর্থাৎ বেশি সমান্তরালতা দিতে পারত। তবু কোনো বড় DBMS এটি ব্যবহার করে না কেন?

প্রথম কারণ গণনার জটিলতা। ১৯৭৯ সালে Christos Papadimitriou Journal of the ACM-এ “The Serializability of Concurrent Database Updates” প্রবন্ধে প্রমাণ করেন, একটি schedule view serializable কি না যাচাই করা NP-complete. অর্থাৎ যতদূর জানা আছে, সবচেয়ে খারাপ ক্ষেত্রে সময় transaction-এর সংখ্যার সাথে সূচকীয় হারে বাড়ে। প্রতি সেকেন্ডে হাজারো transaction সামলানো একটি DBMS প্রতিটি অপারেশনের আগে এমন হিসাব করতে পারে না। conflict-এর পরীক্ষা, উল্টোদিকে, একটি graph-এ cycle খোঁজা — সরল আর দ্রুত।

দ্বিতীয় কারণ লাভ সামান্য। দুই ধারণার পার্থক্য আসে শুধু blind write থেকে, আর বাস্তব অ্যাপে প্রায় সব write আগে পড়ার পর আসে: ব্যালান্স পড়ে বদলানো, মজুত পড়ে কমানো। SQL-এর UPDATE … SET x = x + 1-ও ভেতরে আগে পড়ে। তাই যে বাড়তি schedule view মেনে নিত, তা বাস্তবে বিরল।

তৃতীয় কারণ — schedule আগে থেকে জানা থাকে না। DBMS সিদ্ধান্ত নেয় অপারেশন আসার সঙ্গে সঙ্গে, ভবিষ্যৎ না জেনে। view-এর শর্ত ভবিষ্যতের ওপর নির্ভর করে: একটি write “নিরাপদ” কি না, তা ঠিক হয় পরে কেউ সেই item-এ আবার লিখবে কি না তার ওপর। অনলাইনে সেটি জানা যায় না।

তবু ধারণাটির একটি ব্যবহারিক রূপ টিকে আছে। ১৯৭৯ সালেই Robert Thomas-এর নামে পরিচিত Thomas write rule timestamp-ভিত্তিক concurrency control-এ বলে: কোনো পুরোনো timestamp-এর write যদি এমন item-এ আসে যেখানে নতুন timestamp-এর write ইতিমধ্যে হয়ে গেছে, তাহলে পুরোনোটি abort না করে শুধু উপেক্ষা করা হয় — কারণ সেটি কেউ পড়ত না। এভাবে কিছু view-serializable কিন্তু conflict-serializable নয় এমন schedule মেনে নেওয়া হয়, কোনো NP-complete হিসাব ছাড়াই।

মূল উপলব্ধি: তত্ত্বে বড় সীমানা জানা দরকার, কিন্তু বাস্তব সিস্টেম বাছে এমন সীমানা যা দ্রুত, অনলাইনে আর নিশ্চিতভাবে পরীক্ষা করা যায়।

প্র ০৩একটি হাসপাতালের নিয়ম: রাতে অন্তত একজন ডাক্তার ডিউটিতে থাকবেন। দুজন ডাক্তার একই সময়ে অ্যাপে দেখলেন “আরেকজন আছেন”, আর দুজনেই ছুটি নিলেন। snapshot isolation-এ এটি কীভাবে ঘটে, আর serializability তত্ত্ব দিয়ে কীভাবে ব্যাখ্যা করবেন?

এই উদাহরণের নাম write skew, আর এটি ১৯৯৫ সালে Hal Berenson, Phil Bernstein, Jim Gray ও সহকর্মীদের “A Critique of ANSI SQL Isolation Levels” প্রবন্ধের পর থেকে বিখ্যাত। snapshot isolation-এ প্রতিটি transaction শুরুর মুহূর্তের একটি স্থির ছবি থেকে পড়ে, আর দুজন একই সারিতে লিখলে শুধু তখনই একজনকে বাতিল করা হয়।

T1 (ডাক্তার রহিম) আর T2 (ডাক্তার করিম) — দুজনেই পড়েন রহিম আর করিমের ডিউটির সারি: দুজনই ডিউটিতে, অর্থাৎ ২ জন। T1 নিজের সারিতে লেখে “ছুটি”, T2 নিজের সারিতে। লেখা দুটি আলাদা সারিতে, তাই snapshot isolation কোনো সংঘাত দেখে না; দুটিই commit. ফল: রাতে কেউ নেই, অথচ প্রতিটি transaction একা চললে নিয়ম মানত।

এবার তত্ত্ব। দুটি item: R (রহিমের সারি) আর K (করিমের)। schedule: R1(R) R1(K) R2(R) R2(K) W1(R) W2(K). precedence graph: R2(R) আগে, W1(R) পরে → T2 → T1; R1(K) আগে, W2(K) পরে → T1 → T2. cycle! — conflict serializable নয়। দুটি edge-ই read-write ধরনের (rw-antidependency): প্রত্যেকে এমন কিছু পড়েছে যা অন্যজন পরে বদলেছে। কোনো serial ক্রমে এমন হয় না — যে পরে চলত, সে প্রথমজনের ছুটি দেখত।

এখান থেকেই প্রতিকার এসেছে। Alan Fekete ও সহকর্মীরা ২০০৫ সালে দেখান, snapshot isolation-এর প্রতিটি non-serializable ঘটনার graph-এ পরপর দুটি rw-নির্ভরতা থাকে। Michael Cahill, Uwe Röhm আর Fekete ২০০৮ সালে এই নকশা চালানোর সময় ধরে একটি transaction বাতিল করার কৌশল দেন — serializable snapshot isolation — যা PostgreSQL ৯.১ থেকে SERIALIZABLE level-এ ব্যবহার করে। Oracle-এর SERIALIZABLE এখনো snapshot isolation; সেখানে প্রতিকার হাতে: SELECT … FOR UPDATE দিয়ে পড়া সারি lock করা, বা নিয়মটিকে একটি সারিতে বেঁধে সংঘাত তৈরি করা।

মূল উপলব্ধি: precedence graph শুধু পরীক্ষার খাতার জিনিস নয় — আধুনিক DBMS-এর সবচেয়ে সূক্ষ্ম bug-টি ঠিক এই graph-এর একটি cycle, আর তার প্রতিকারও সেই graph-এর নকশা চিনে।

প্র ০৪Cascadeless হলেই abort নিরাপদ — তবু প্রায় সব DBMS আরও কড়া strict schedule বেছে নেয় কেন? এর দাম কী?

উত্তর লুকিয়ে আছে recovery-র যন্ত্রে। পর্ব ৪৮-এ দেখবেন, DBMS প্রতিটি write-এর আগে লগে লেখে পুরোনো মান (before-image) আর নতুন মান। abort বা crash-এর পর undo মানে লগ উল্টো দিকে পড়ে পুরোনো মান বসিয়ে দেওয়া। এই সরল নিয়মটি সঠিক শুধু তখনই, যখন অসমাপ্ত কোনো লেখার ওপর অন্য কেউ লেখেনি।

উদাহরণ নিন W1(A) W2(A) A1, A-র প্রাথমিক মান ১০. T1 লেখে ২০, T2 লেখে ৩০. কেউ dirty read করেনি, তাই schedule cascadeless. এখন T1 abort করলে before-image ১০ বসালে T2-এর ৩০ মুছে যায় — অথচ T2 এখনো চলছে, পরে commit-ও করতে পারে। সঠিক কাজ হতো কিছুই না করা, কারণ A-র বর্তমান মান T2-এর। আবার যদি T2-ও পরে abort করে, তার before-image ২০ — T1-এর বাতিল লেখা — ফিরে আসত! এসব ঠিক করতে recovery-কে প্রতিটি item-এর লেখার পুরো ইতিহাস মনে রেখে বিচার করতে হয়। strict schedule এই সমস্যাকে জন্মই নিতে দেয় না: একটি item-এ একসাথে একজনেরই অসমাপ্ত লেখা থাকে।

১৯৯২ সালে IBM-এর C. Mohan ও সহকর্মীদের ARIES — আজকের বহু DBMS-এর recovery-র নকশা — strict 2PL-এর মতো lock-ব্যবস্থার ওপর নির্ভর করে লগ-ভিত্তিক undo সরল রেখেছে।

দাম হলো অপেক্ষা। strict-এ exclusive lock ধরে রাখতে হয় commit পর্যন্ত, আর commit মানে লগ ডিস্কে পৌঁছানো — অনেক সময় মিলিসেকেন্ড। একটি জনপ্রিয় item (যেমন একটি বড় মার্চেন্টের ব্যালান্স সারি) সেই সময় পুরো আটকে থাকে। DBMS এই দাম কমায় group commit দিয়ে (অনেক transaction-এর লগ একবারে ডিস্কে), আর MVCC দিয়ে, যেখানে পাঠকেরা পুরোনো সংস্করণ পড়ে লেখকের অপেক্ষা করে না।

মূল উপলব্ধি: concurrency control আর recovery আলাদা অধ্যায় হলেও আলাদা যন্ত্র নয়। strict schedule হলো দুই দলের চুক্তি — যাতে recovery-র নিয়ম সরল থাকে, বিনিময়ে কিছু অপেক্ষা।

প্র ০৫আপনি bKash-এ বন্ধুকে টাকা পাঠালেন, “সফল” দেখলেন, তারপর ফোন করে বললেন ব্যালান্স দেখতে। বন্ধু পুরোনো ব্যালান্স দেখলেন। সিস্টেমটি কি serializable নিয়ম ভেঙেছে?

শুধু serializability-র সংজ্ঞায় — না, অবাক করার মতো হলেও। serializability বলে ফল কোনো এক serial ক্রমের সমান হবে; সেই ক্রম ঘড়ির সময়ের ক্রম হতে হবে, এমন কথা সংজ্ঞায় নেই। বন্ধুর “ব্যালান্স দেখা” transaction-কে আপনার “টাকা পাঠানো”-র আগে বসিয়ে দিলে একটি বৈধ serial ক্রম পাওয়া যায়, আর বন্ধুর দেখা পুরোনো মান সেই ক্রমের সাথে মেলে। এটি ঘটতে পারে, যেমন, বন্ধুর পড়া যদি একটু পিছিয়ে থাকা কোনো replica থেকে আসে (পর্ব ৫২)।

যে বাড়তি নিশ্চয়তা আমরা স্বাভাবিকভাবে আশা করি, তার নাম strict serializability: যদি একটি transaction অন্যটি শুরু হওয়ার আগেই শেষ হয়ে যায়, তাহলে serial ক্রমেও সে আগে থাকবে। এটি serializability আর linearizability-র মিলন; linearizability ধারণাটি Maurice Herlihy আর Jeannette Wing ১৯৯০ সালে একক object-এর জন্য সংজ্ঞায়িত করেন। এক যন্ত্রে চলা rigorous 2PL — সব lock commit পর্যন্ত ধরে রাখা — এমনিতেই এটি দেয়: commit-এর আগে কোনো lock ছাড়া হয় না, তাই সময়ের ক্রম আর serial ক্রম মেলে।

বিতরণ করা সিস্টেমে ব্যাপারটি কঠিন, কারণ বিভিন্ন যন্ত্রের ঘড়ি পুরোপুরি মেলে না। Google-এর Spanner (২০১২ সালের OSDI প্রবন্ধ) এর নাম দেয় external consistency, আর TrueTime নামের একটি ঘড়ি-API দিয়ে তা দেয় — GPS আর atomic clock দিয়ে প্রতিটি সময়কে একটি ছোট অনিশ্চয়তার পরিসর হিসেবে জানিয়ে, আর commit-এর পর সেই অনিশ্চয়তা পার না হওয়া পর্যন্ত অপেক্ষা করে। অনেক distributed database আবার ইচ্ছে করে দুর্বল নিশ্চয়তা দেয়, গতির বিনিময়ে।

তাই প্রশ্নটির সৎ উত্তর: নির্ভর করে সিস্টেম কোন প্রতিশ্রুতি দিয়েছে তার ওপর। ব্যবহারকারীর অভিজ্ঞতার জন্য অনেক সিস্টেম অন্তত “নিজের লেখা নিজে দেখা” (read-your-writes) দেয়, কিন্তু অন্যের দেখার নিশ্চয়তা আলাদা কথা।

মূল উপলব্ধি: serializability সঠিকতার কথা বলে, সময়ের নয়। “সফল দেখার পর সবাই নতুন মান দেখবে” চাইলে strict serializability বা external consistency লাগে — আর সেটি একটি আলাদা, দামি প্রতিশ্রুতি।

অনুশীলন

একটি কোয়েরি লেখা, একটি schedule হাতে বিশ্লেষণ, একটি ফল আগে বলা, একটি view-এর যুক্তি।

  1. কোয়েরি লিখুন: ops(pos, tx, op, item) টেবিলে একটি schedule রাখা। view equivalence যাচাইয়ের দুটি উপাদান বের করুন: (ক) প্রতিটি read কার লেখা পড়ে — না থাকলে “প্রাথমিক মান”; (খ) প্রতিটি item-এর শেষ write কোন transaction-এর। অংশ ৫-এর চার-transaction-এর schedule-এ চালালে কী আসবে?
    SELECT r.op || r.tx || '(' || r.item || ')' AS read_op,
           COALESCE('T' || (SELECT w.tx FROM ops w
                            WHERE w.item = r.item AND w.op = 'W' AND w.pos < r.pos
                            ORDER BY w.pos DESC LIMIT 1), 'প্রাথমিক মান') AS reads_from
    FROM ops r WHERE r.op = 'R' ORDER BY r.pos;
    
    SELECT item, 'T' || tx AS final_writer FROM ops o
    WHERE op = 'W' AND pos = (SELECT MAX(pos) FROM ops WHERE item = o.item AND op = 'W')
    ORDER BY item;

    (ক) R2(A) | প্রাথমিক মান, R1(A) | T2, R4(B) | T2, R3(C) | T1, R3(D) | T4. (খ) A | T2, B | T2, C | T1, D | T4. correlated subquery (পর্ব ২১) প্রতিটি read-এর আগের শেষ write খোঁজে; কোনোটি না পেলে NULL, আর COALESCE তাকে “প্রাথমিক মান” বানায়। দুটি schedule-এ এই দুই তালিকা আর প্রাথমিক read মিললে তারা view equivalent. (একই transaction-এর নিজের লেখা পড়াও এখানে গোনা হয় — সংজ্ঞাতেও তা-ই।)

  2. হাতে বিশ্লেষণ (trace): R1(X) R2(Z) R1(Z) R3(X) R3(Y) W1(X) C1 W3(Y) C3 R2(Y) W2(Z) W2(Y) C2. (ক) precedence graph আঁকুন; (খ) conflict serializable হলে serial ক্রম দিন; (গ) recoverable, cascadeless, strict — কোনগুলো?

    (ক) X: R3(X) আগে, W1(X) পরে → T3 → T1 (R1(X) আর R3(X) দুটিই read). Y: R3(Y) বা W3(Y) আগে, R2(Y) আর W2(Y) পরে → T3 → T2. Z: R1(Z) আগে, W2(Z) পরে → T1 → T2 (R2(Z) T2-এর নিজের)। edge তিনটি: T3 → T1, T3 → T2, T1 → T2.

    (খ) cycle নেই — conflict serializable; একমাত্র topological ক্রম T3, T1, T2. পাতার চেকারেও একই উত্তর আসে।

    (গ) একমাত্র read-from: R2(Y) পড়ে W3(Y). C3 আসে R2(Y)-এর আগেই — তাই recoverable আর cascadeless. strict: W1(X)-এর পর C1-এর আগে কেউ X ছোঁয় না; W3(Y)-এর পর C3-এর আগে কেউ Y ছোঁয় না; T2-এর লেখার পর আর কেউ নেই। তিনটিই — schedule strict.

  3. ফল আগে বলুন: অংশ ৫-এর JavaScript চেকারের প্রথম লাইনে "R1(A) R2(A) W1(B) W2(B) R3(B)" দিলে ঠিক কোন লাইনগুলো ছাপা হবে, কোন ক্রমে? চালানোর আগে লিখে রাখুন, তারপর মিলিয়ে দেখুন।

    চারটি লাইন:

    W1(B) আগে, W2(B) পরে → edge T1→T2
    W1(B) আগে, R3(B) পরে → edge T1→T3
    W2(B) আগে, R3(B) পরে → edge T2→T3
    Conflict serializable ✓  সমতুল্য serial ক্রম: T1 → T2 → T3

    R1(A) আর R2(A) দুটিই read, তাই A থেকে কোনো edge নেই। চেকার বাইরের loop-এ আগের অপারেশন ধরে পরেরগুলোর সাথে মেলায়, তাই W1(B)-এর দুটি জোড়া আগে ছাপা হয়। Kahn-এ শুরুতে শুধু T1-এর দিকে কোনো edge আসে না; T1 সরালে T2 প্রস্তুত হয়, T2 সরালে T3.

  4. View-এর যুক্তি: W1(A) W2(A) W2(B) W1(B) W3(B) — কোনো read নেই। (ক) conflict serializable কি? (খ) view serializable কি? হলে কোন serial ক্রম?

    (ক) W1(A), W2(A) → T1 → T2; W2(B), W1(B) → T2 → T1 — cycle, তাই না. (এছাড়া T1 → T3 আর T2 → T3.)

    (খ) কোনো read নেই, তাই প্রথম দুটি শর্ত আপনা-আপনি মেলে; শুধু শেষ write মেলাতে হবে। S-এ A-র শেষ write T2-এর, B-র শেষ write T3-এর। serial ক্রমে A-র শেষ writer T2 হতে T2-কে T1-এর পরে থাকতে হবে, আর B-র জন্য T3 সবার শেষে। T1, T2, T3 দুটিই মেটায় — তাই view serializable. সব write-ই blind, ঠিক যেমন উপপাদ্য বলে: পার্থক্য আসে blind write থেকে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি schedule conflict serializable কি না যাচাই করে serial ক্রম বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. প্রতিটি transaction-এর জন্য একটি node আঁকা
  2. সব conflicting জোড়া খোঁজা — একই item, আলাদা transaction, অন্তত একটি write
  3. প্রতিটি জোড়ার জন্য আগের transaction থেকে পরেরটির দিকে edge
  4. Graph-এ cycle আছে কি না দেখা
  5. Cycle না থাকলে topological sort করা
  6. সেই ক্রমে transaction সাজিয়ে সমতুল্য serial schedule লেখা

একটি schedule-এ নিচের কোন অপারেশন-জোড়াটি conflicting?

  • R1(A) আর R2(A)
  • R1(A) আর W2(A)
  • W1(A) আর R1(A)
  • W1(A) আর W2(B)

একটি schedule-এর precedence graph-এর edge: T2 → T1, T2 → T3, T1 → T3. সমতুল্য serial ক্রম কোনটি?

  • T1, T2, T3
  • T2, T3, T1
  • T2, T1, T3
  • কোনোটিই নয়, কারণ graph-এ cycle আছে

Schedule R1(A) W2(A) W1(A) W3(A) সম্পর্কে কোনটি সত্য?

  • Conflict serializable, ক্রম T1, T2, T3
  • এটি নিজেই একটি serial schedule
  • Conflict বা view — কোনোভাবেই serializable নয়
  • View serializable, কিন্তু conflict serializable নয়

Schedule W1(A) R2(A) C2 A1 — T2 পড়ে T1-এর লেখা A, T2 commit করে, তারপর T1 abort করে। এই schedule কোন শ্রেণির?

  • Non-recoverable
  • Recoverable, কিন্তু cascadeless নয়
  • Cascadeless
  • Strict

Strict two-phase locking (exclusive lock commit বা abort পর্যন্ত ধরে রাখা) কোন নিশ্চয়তা দেয়?

  • Conflict serializable আর deadlock-মুক্ত schedule
  • Conflict serializable আর strict schedule
  • সব view serializable schedule
  • শুধু recoverable schedule, serializability নয়

দুটি transaction-এর প্রতিটিতে ৪টি অপারেশন (commit বাদে)। প্রতিটির নিজের ক্রম অটুট রেখে মোট কতগুলো আলাদা schedule সম্ভব, আর তার কয়টি serial?

  • ৪০,৩২০টি, তার ২টি serial
  • ১৬টি, তার ২টি serial
  • ৭০টি, তার ২টি serial
  • ৭০টি, তার ৪টি serial

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Schedule = অপারেশনের আসল ক্রম। পালা করে চালানো throughput বাড়ায়; সঠিক হলো সেই schedule, যা কোনো এক serial ক্রমের সমতুল্য।
  2. Conflict: আলাদা transaction, একই item, অন্তত একটি write. non-conflicting পাশাপাশি জোড়া অদলবদল করে serial বানানো গেলে conflict serializable.
  3. Precedence graph: প্রতিটি conflicting জোড়ায় আগের থেকে পরের transaction-এ edge; cycle নেই ⇔ conflict serializable; topological sort = serial ক্রম (একাধিক হতে পারে)।
  4. View: প্রাথমিক read, read-from, শেষ write মেলে। conflict ⊂ view; পার্থক্য blind write থেকে; যাচাই NP-complete.
  5. Recoverability আলাদা প্রশ্ন: recoverable (writer আগে commit) ⊃ cascadeless (commit-এর আগে পড়া নয়) ⊃ strict (commit-এর আগে পড়া-লেখা নয়)। strict 2PL দুই দিকই দেয়।

পরের পর্বে: কোন schedule নিরাপদ, তা জানা হলো। পর্ব ৪৬-এ দেখবেন DBMS কীভাবে শুধু নিরাপদ schedule ঘটতে দেয় — shared আর exclusive lock, 2PL-এর রূপভেদ, deadlock ধরা আর ভাঙা, আর timestamp-ভিত্তিক বিকল্প।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান