SAVEPOINT — qisman rollback
Muammodan boshlaylik
Admin Excel fayldan 500 ta mahsulotni import qilyapti. Hammasi bitta tranzaksiyada — chunki import oxirida umumiy hisob yangilanishi kerak. 347-qatorda xato: narx ustunida "kelishiladi" yozilgan.
Variantlar:
ROLLBACK— 346 ta to'g'ri qator ham yo'qoladi, admin hammasini qaytadan boshlaydiCOMMIT— lekin 347-qator yarim-tayyor holatda qolishi mumkin (masalan, mahsulot qo'shildi, lekin uning teglari qo'shilmadi)
Kerak — faqat 347-qatorga tegishli o'zgarishlarni bekor qilish va importni davom ettirish. Buning uchun — SAVEPOINT.
Sintaksis
START TRANSACTION;
INSERT INTO buyurtmalar (mijoz_id) VALUES (2);
SAVEPOINT qator_qoshish; -- 📍 nuqta belgilandi
INSERT INTO buyurtma_qatorlari ... ;
UPDATE mahsulotlar SET soni = soni - 1 ... ;
ROLLBACK TO SAVEPOINT qator_qoshish; -- ↩ nuqtagacha qaytish
-- buyurtmalar dagi INSERT saqlanib qoldi, qolgani bekor qilindi
-- tranzaksiya hali ham ochiq
COMMIT;
| Buyruq | Ma'nosi |
|---|---|
SAVEPOINT nom |
Joriy holatni "eslab qolish" |
ROLLBACK TO SAVEPOINT nom |
Shu nuqtadan keyingi o'zgarishlarni bekor qilish; tranzaksiya davom etadi |
RELEASE SAVEPOINT nom |
Nuqtani o'chirish (o'zgarishlar saqlanadi) |
ROLLBACK |
Butun tranzaksiyani bekor qilish (barcha savepoint lar bilan) |
COMMIT |
Hammasini saqlash |
Hayotiy o'xshatma: o'yindagi "save". Bossni yengolmadingiz — butun o'yinni qaytadan emas, oxirgi saqlangan joydan boshlaysiz.
Import misoli
START TRANSACTION;
-- 1-mahsulot
SAVEPOINT m1;
INSERT INTO mahsulotlar (nom, narx, soni) VALUES ('Mahsulot A', 150000, 10);
INSERT INTO mahsulot_teglar (mahsulot_id, teg_id) VALUES (LAST_INSERT_ID(), 1);
RELEASE SAVEPOINT m1; -- muvaffaqiyatli
-- 2-mahsulot
SAVEPOINT m2;
INSERT INTO mahsulotlar (nom, narx, soni) VALUES ('Mahsulot B', 90000, 5);
INSERT INTO mahsulot_teglar (mahsulot_id, teg_id) VALUES (LAST_INSERT_ID(), 999);
-- ❌ ERROR 1452: foreign key — 999-teg yo'q
ROLLBACK TO SAVEPOINT m2; -- faqat "Mahsulot B" bekor qilindi
-- 3-mahsulot
SAVEPOINT m3;
...
COMMIT; -- A va C saqlandi, B — yo'q
Ilovada (PHP):
<?php
$pdo->beginTransaction();
$xatolar = [];
foreach ($qatorlar as $i => $q) {
$pdo->exec("SAVEPOINT q$i");
try {
$stmt = $pdo->prepare('INSERT INTO mahsulotlar (nom, narx, soni) VALUES (?, ?, ?)');
$stmt->execute([$q['nom'], $q['narx'], $q['soni']]);
$id = $pdo->lastInsertId();
foreach ($q['teglar'] as $tegId) {
$pdo->prepare('INSERT INTO mahsulot_teglar VALUES (?, ?)')->execute([$id, $tegId]);
}
$pdo->exec("RELEASE SAVEPOINT q$i");
} catch (PDOException $e) {
$pdo->exec("ROLLBACK TO SAVEPOINT q$i");
$xatolar[] = ($i + 1) . "-qator: " . $e->getMessage();
}
}
$pdo->commit();
// Natija: "498 ta import qilindi, 2 ta xato: 347-qator: ..., 412-qator: ..."
Har bir qator — "mini-tranzaksiya": yo to'liq qo'shiladi (mahsulot + teglari), yo umuman qo'shilmaydi. Butun import esa bitta katta tranzaksiya.
Ichma-ich tranzaksiyalar
MySQL haqiqiy ichma-ich tranzaksiyalarni qo'llab-quvvatlamaydi:
START TRANSACTION;
INSERT ...;
START TRANSACTION; -- ⚠️ oldingi tranzaksiyani yashirin COMMIT qiladi!
Ikkinchi START TRANSACTION birinchisini jimgina yakunlaydi. Bu kutilmagan xatti-harakat.
Savepoint lar ichma-ich tranzaksiya o'rnini bosadi. Ko'p ORM va freymvorklar aynan shunday qiladi: tashqi transaction() ichida yana transaction() chaqirilsa — haqiqiy tranzaksiya emas, SAVEPOINT yaratiladi:
// Laravel
DB::transaction(function () {
// START TRANSACTION
DB::transaction(function () {
// SAVEPOINT trans2
});
// RELEASE SAVEPOINT trans2
});
// COMMIT
Cheklovlar
- Savepoint faqat tranzaksiya ichida ishlaydi
ROLLBACK TOdan keyin undan keyin yaratilgan savepoint lar o'chadi- Savepoint qulflarni ochmaydi —
ROLLBACK TOdan keyin ham o'sha qatorlar qulflangan qoladi - Bir xil nomli yangi savepoint eskisini almashtiradi
- DDL (
ALTER,CREATE) baribir butun tranzaksiyani yashirinCOMMITqiladi
Qachon kerak
✅ Katta import / qayta ishlash — xato qatorlarni tashlab, qolganini saqlash
✅ Freymvorklarda ichma-ich transaction() chaqiruvlari
✅ Murakkab biznes jarayoni — ixtiyoriy qadam muvaffaqiyatsiz bo'lsa ham asosiy qism saqlansin
❌ Oddiy CRUD — oddiy COMMIT / ROLLBACK yetarli
Nima uchun shunday?
Nima uchun savepoint ga qaytish qulflarni ochmaydi?
Chunki tranzaksiya hali ochiq va u yana o'sha qatorlarga murojaat qilishi mumkin. Agar qulf ochilsa — boshqa tranzaksiya qatorni o'zgartirib qo'yishi, va bizning tranzaksiyamiz qaytib kelganda butunlay boshqa ma'lumotni ko'rishi mumkin edi. Izolatsiya kafolati buziladi. Qulflar faqat butun tranzaksiya tugaganda (COMMIT yoki to'liq ROLLBACK) ochiladi. Shuning uchun savepoint li uzoq tranzaksiyalar ham qisqa bo'lishga harakat qilishi kerak.
Xulosa
Eslab qoling:
SAVEPOINT nom— tranzaksiya ichida nuqta belgilashROLLBACK TO SAVEPOINT nom— nuqtadan keyingilarini bekor qilish, tranzaksiya davom etadiRELEASE SAVEPOINT nom— nuqtani o'chirish- Import: har bir qator uchun savepoint — xatolarni tashlab, qolganini saqlash
- MySQL da ichma-ich
START TRANSACTIONyo'q — ikkinchisi birinchisiniCOMMITqiladi - Freymvorklarning ichma-ich
transaction()— savepoint lar orqali - Savepoint qulflarni ochmaydi
Keyingi dars
40-dars: VIEW — yaratish, yangilash, o'chirish, qachon kerak