နားလည်ထားရမယ့် အချက်
Transaction ဆိုတာ operation များစွာကို တစ်ခုတည်းအနေနှင့် အောင်မြင် သို့မဟုတ် ကျရှုံးအောင် အုပ်စုဖွဲ့ပေးတာပါ - Account A ကနေ နုတ်ပြီး Account B ကို ထည့်တာက နှစ်ခုစလုံး ဖြစ်ရမယ်။
BEGIN
Transaction တစ်ခု စတင်ပါတယ်
Operation A + B
Debit နှင့် credit operation နှစ်ခုကို run လုပ်ပါတယ်
COMMIT သို့ ROLLBACK
အားလုံးအောင်မြင်ရင် COMMIT၊ တစ်ခုခုမအောင်မြင်ရင် ROLLBACK
- Atomicity - all-or-nothing
- Consistency - valid state မှ valid state သို့
- Isolation - concurrent transaction တွေ မမျှော်လင့်ထားသလို အနှောင့်အယှက် မဖြစ်
- Durability - commit ဖြစ်ပြီးနောက် ကျန်ရှိနေခြင်း
Concurrency - user များစွာက တစ်ချိန်တည်း ဖတ်/ရေးနေခြင်း - က lost update, stale read လို ပြဿနာတွေ ဖြစ်စေနိုင်ပါတယ်။
Isolation က transaction တွေ တစ်ခုနှင့်တစ်ခု ဘယ်လောက် မြင်နိုင်လဲကို ထိန်းချုပ်ပေးပါတယ်။ PostgreSQL ရဲ့ transaction/MVCC သင်ခန်းစာနှင့် Redis ရဲ့ transaction သင်ခန်းစာမှာ လက်တွေ့ ဆက်လေ့လာနိုင်ပါတယ်။
- Transaction
- Database operation များစွာကို တစ်ခုတည်းအနေနှင့် အောင်မြင် သို့မဟုတ် ကျရှုံးစေသော အုပ်စု။
- ACID
- Transaction တစ်ခု ပေးသင့်သော အာမခံချက် လေးခု - Atomicity, Consistency, Isolation, Durability။
BANK TRANSFER TRANSACTION FLOW
------------------------------
BEGIN
|
v
Operation A: debit Account A (balance -= amount)
|
v
Operation B: credit Account B (balance += amount)
|
v
Did both operations succeed and stay valid?
|
+--YES--> COMMIT (both changes are kept)
|
+--NO ---> ROLLBACK (both changes undone,
as if nothing happened)လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
ဆက်စပ်နေတဲ့ write တွေအားလုံး အောင်မြင် သို့မဟုတ် ကျရှုံးရမယ်ဆိုရင် transaction တစ်ခုအဖြစ် wrap လုပ်ပါ - order create + stock decrement, point transfer စတာတွေမှာ ပုံမှန် ကြုံရတတ်ပါတယ်။
Framework/library အများစုက transaction helper ပေးထားလို့ raw BEGIN/COMMIT/ROLLBACK ကို လက်နှင့်ရေးဖို့ ရှားပါးပါတယ် - ဒါပေမယ့် အောက်ခံမှာ ဘာဖြစ်နေလဲ နားလည်ထားတာ ကူညီပါတယ်။
ဘယ် operation တွေကို အုပ်စုဖွဲ့ရမလဲဆိုတာ တွက်ချက်ပါ - မဆက်စပ်တဲ့ work များစွာကို transaction ရှည်ရှည်ကြီးထဲ wrap လုပ်တာက concurrency ကို ထိခိုက်စေနိုင်ပါတယ်။
အောက်က runnable ဥပမာက bank-transfer transaction ကို simulate လုပ်ပြီး၊ ငွေမလုံလောက်ရင် balance နှစ်ခုစလုံးကို မထိထားသလို rollback လုပ်ပေးပါတယ်။
အတူတူ စမ်းရေးကြည့်မယ်
function transferFunds(accounts, fromId, toId, amount) {
const original = accounts.map((a) => ({ ...a }));
const from = accounts.find((a) => a.id === fromId);
const to = accounts.find((a) => a.id === toId);
from.balance -= amount;
to.balance += amount;
if (from.balance < 0) {
for (const a of accounts) {
const o = original.find((x) => x.id === a.id);
a.balance = o.balance;
}
return {
status: "ROLLBACK",
reason: "Insufficient funds",
accounts: accounts.map((a) => ({ ...a })),
};
}
return { status: "COMMIT", accounts: accounts.map((a) => ({ ...a })) };
}
const accountsA = [{ id: "A", balance: 500 }, { id: "B", balance: 100 }];
console.log("Successful transfer:", transferFunds(accountsA, "A", "B", 200));
const accountsB = [{ id: "A", balance: 50 }, { id: "B", balance: 100 }];
console.log("Rolled-back transfer:", transferFunds(accountsB, "A", "B", 200));Successful transfer: { status: 'COMMIT', accounts: [ {id:'A',balance:300}, {id:'B',balance:300} ] }
Rolled-back transfer: { status: 'ROLLBACK', reason: 'Insufficient funds', accounts: [ {id:'A',balance:50}, {id:'B',balance:100} ] }
ပထမ case က balance 500 ရှိလို့ 200 ကို ပို့ပြီးအောင်မြင်တယ်; ဒုတိယ case က balance 50 ပဲရှိလို့ 200 ပို့ရင် negative ဖြစ်မှာမို့ rollback လုပ်ပြီး original balance အတိုင်း ပြန်ရောက်တယ်။၅ မိနစ် စမ်းကြည့်
transferFunds ကို ချဲ့ပြီး transferHistory log array တစ်ခု ထည့်ပါ - COMMIT ဖြစ်တိုင်း entry တစ်ခု ထည့်ပြီး ROLLBACK ဖြစ်ရင် ဘာမှ မထည့်ပါနှင့်။
သတိလေးတစ်ချက်
ဆက်စပ်နေသော write များကို transaction ဖြင့် မအုပ်စုဖွဲ့ဘဲ ချန်ထားပြီး တစ်ဝက်တစ်ပျက် update ဖြစ်နိုင်ခြင်း
မဆက်စပ်သည့် operation များစွာကို transaction ရှည်ရှည်ကြီးတစ်ခုထဲ ထည့်ပြီး concurrency ကို မလိုအပ်ဘဲ ထိခိုက်စေခြင်း
Wikipedia: ACID — How Databases Work