နားလည်ထားရမယ့် အချက်
ဒီပရောဂျက်က ယခင်သင်ခန်းစာသုံးခုကို တစ်ပြိုင်နက်တည်း ကျင့်သုံးစေသည် - schema အတွက် tables/keys/relationships; table ကြီးထွားလာသည်နှင့်အမျှ မြန်ဆန်နေအောင် indexing; multi-step write တစ်ခု လုံးဝဖြစ်ပါစေ ဒါမှမဟုတ် လုံးဝမဖြစ်ပါစေ ACID/transactions။
- Tables, keys, relationships - schema ကိုယ်တိုင်
- Indexing - table ကြီးလာလည်း query မြန်နေအောင်
- Transactions & ACID - multi-step write အားလုံးဒါမှမဟုတ်တစ်ခုမှမရှိ
E-commerce ဟာ ဥပမာအစစ်ဖြစ်တယ် - 'order တင်မယ်' ခလုတ်တစ်ချက်နှိပ်ခြင်းက table များစွာကို တစ်ပြိုင်နက်ထိတွေ့ပြီး write တစ်ခုမှားရင် ငွေကြေးနှင့်ဆိုင်တဲ့ ပြဿနာဖြစ်နိုင်ပါတယ်။
Order တစ်ခုမှာ product များစွာ ပါနိုင်ပြီး product တစ်ခုကလည်း order များစွာမှာ ပါနိုင်တာကြောင့် join table Order_Items လိုအပ်ပါတယ် - ၎င်းက quantity နှင့် ဝယ်ယူချိန် price ကို ပါဆောင်ပါတယ်။
Order_Items ကို order_id နှင့် product_id နှစ်ခုလုံးဖြင့် အမြဲ query လုပ်မှာဖြစ်တာကြောင့် နှစ်ခုစလုံးအတွက် index လိုအပ်ပါတယ် - မဟုတ်ရင် table တစ်ခုလုံးကို scan လုပ်ရပါလိမ့်မယ်။
All-or-nothing
Product တစ်ခု stock ကုန်သွားရင် order အတွက် ကြိုးစားထားသမျှ write အားလုံး rollback ဖြစ်ပြီး stock count တွေ မလုပ်မီကအတိုင်း ကျန်ရှိရမည် - ဘဏ်စာရင်းလွှဲပြောင်းမှုနည်းတူ ACID guarantee ပါပဲ။
သင်ခန်းစာသုံးခုက ဘယ်လိုအထပ်ထပ်ထားလဲ - keys/relationships က table ပုံသဏ္ဍာန် ဆုံးဖြတ်ပြီး indexing က မြန်နှုန်း ဆုံးဖြတ်ပြီး ACID က ကျရှုံးမှုတွင် ဘာဖြစ်မလဲ ဆုံးဖြတ်ပါတယ်။
E-COMMERCE SCHEMA AND TRANSACTION FLOW
--------------------------------------
E-COMMERCE SCHEMA
-------------------------
USERS
user_id PK
name
PRODUCTS
product_id PK
name
price
stock
ORDERS
order_id PK
user_id FK -> USERS.user_id
ORDER_ITEMS
order_item_id PK
order_id FK -> ORDERS.order_id
product_id FK -> PRODUCTS.product_id
quantity
price_at_purchase
RELATIONSHIPS
USERS 1---* ORDERS
ORDERS 1---* ORDER_ITEMS *---1 PRODUCTS
TRANSACTION FLOW: createOrder(user, items)
-------------------------------------------
BEGIN
check stock for every item first
if any item short -> ROLLBACK (no writes)
else:
create ORDERS row
create ORDER_ITEMS rows
decrement PRODUCTS.stock
COMMITလက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
Schema ဒီဇိုင်းနှင့် transaction simulation နှစ်ခုလုံးကို ဒီအစီအစဉ်အတိုင်း လုပ်ဆောင်ပါ -
Users နှင့် Products ကို ဒီဇိုင်းဆွဲပါ
Table နှစ်ခုစလုံး သီးခြားရပ်တည်ပြီး တခြား table တွေ ၎င်းတို့ကို ညွှန်းသွားမည်။
Orders ထည့်ပါ
order_id PK နှင့် Users ကို ညွှန်းသော user_id FK ရှိသည်။
Order_Items ကို join table အဖြစ် ထည့်ပါ
order_id, product_id, quantity, price_at_purchase ပါဝင်သည် - price ပြောင်းလဲသည့်တိုင် history မပြောင်း။
Join table ကို index လုပ်ပါ
order_id နှင့် product_id နှစ်ခုလုံးအတွက် index ထည့်ပါ။
ဘာမှမရေးမီ stock ကို အရင်စစ်ပါ
Transaction ကို begin လုပ်ပြီး item တိုင်း stock လုံလောက်မလုံလောက် အရင်စစ်ပါ။
Commit ဒါမှမဟုတ် rollback
လုံလောက်ရင် row တွေဖန်တီးပြီး stock လျှော့ချကာ commit; မလုံလောက်ရင် write လုံးဝမလုပ်ဘဲ rollback ပြန်ပါ။
သက်သေပြပါ
Inventory တူတူကနေ အောင်မြင်တဲ့ order နှင့် fail ဖြစ်တဲ့ order နှစ်ခုလုံးကို run ပြီး stock ပြောင်းလဲမှု ကွာခြားချက်ကို ယှဉ်ကြည့်ပါ။
အတူတူ စမ်းရေးကြည့်မယ်
const products = {
p1: { name: "Mechanical Keyboard", price: 49.99, stock: 5 },
p2: { name: "Wireless Mouse", price: 19.99, stock: 2 }
};
const orders = [];
const orderItems = [];
let nextOrderId = 1;
function createOrder(userId, items) {
// BEGIN TRANSACTION
for (const item of items) {
const product = products[item.productId];
if (!product || product.stock < item.qty) {
// ROLLBACK: no writes have been applied yet
return {
status: "ROLLBACK",
reason: `insufficient stock for ${item.productId} (requested ${item.qty}, have ${product ? product.stock : 0})`
};
}
}
const orderId = nextOrderId++;
const createdItems = [];
for (const item of items) {
products[item.productId].stock -= item.qty;
createdItems.push({
orderId,
productId: item.productId,
qty: item.qty,
price: products[item.productId].price
});
}
orders.push({ orderId, userId });
orderItems.push(...createdItems);
// COMMIT
return { status: "COMMIT", orderId, items: createdItems };
}
console.log("Order 1 (sufficient stock):");
console.log(JSON.stringify(createOrder("u1", [{ productId: "p1", qty: 2 }])));
console.log("Stock after order 1:", JSON.stringify(products));
console.log("\nOrder 2 (insufficient stock):");
console.log(JSON.stringify(createOrder("u2", [{ productId: "p2", qty: 5 }])));
console.log("Stock after order 2 (unchanged):", JSON.stringify(products));
Order 1 (sufficient stock):
{"status":"COMMIT","orderId":1,"items":[{"orderId":1,"productId":"p1","qty":2,"price":49.99}]}
Stock after order 1: {"p1":{"name":"Mechanical Keyboard","price":49.99,"stock":3},"p2":{"name":"Wireless Mouse","price":19.99,"stock":2}}
Order 2 (insufficient stock):
{"status":"ROLLBACK","reason":"insufficient stock for p2 (requested 5, have 2)"}
Stock after order 2 (unchanged): {"p1":{"name":"Mechanical Keyboard","price":49.99,"stock":3},"p2":{"name":"Wireless Mouse","price":19.99,"stock":2}}၅ မိနစ် စမ်းကြည့်
Transaction function ကို ပြင်ဆင်ပြီး quantity သည် positive integer ဖြစ်ရမည်ဆိုသည့် စစ်ဆေးမှုကို stock ထိမတိုင်မီ ထပ်ထည့်ပါ။ quantity 0 ဖြင့် test case တစ်ခု ထည့်ကာ stock မလုံလောက်ခြင်းအတွက် rollback ဖြစ်သလို quantity 0 အတွက်လည်း အလားတူ rollback ဖြစ်ကြောင်း စစ်ကြည့်ပါ။
သတိလေးတစ်ချက်
Item တိုင်း stock အရင်မစစ်ဘဲ loop ထဲမှာ item တစ်ခုစီအတွက် stock ကို တစ်ခုချင်း လျှော့ချသွားခြင်း - ဒါက product တချို့ လျှော့ချပြီး တချို့ ငြင်းပယ်ခံရနိုင်ပြီး ACID က တားဆီးလိုသည့် partial write အတိုင်းပါပဲ ဖြစ်စေနိုင်သည်
Order_Items ကို ကိုယ်ပိုင် join table အဖြစ် မဟုတ်ဘဲ Orders မှ Products သို့ တိုက်ရိုက် foreign key အဖြစ် model လုပ်ခြင်း - quantity နှင့် ဝယ်ယူချိန် price ကို line item တစ်ခုစီအလိုက် သိမ်းဆည်းနိုင်စွမ်း ဆုံးရှုံးသွားသည်
PostgreSQL Documentation: Transactions — How Databases Work