SAVEPOINT — qisman rollback

~10 daq · MySQL · Transaction va Qulflar

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 boshlaydi
  • COMMIT — 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 TO dan keyin undan keyin yaratilgan savepoint lar o'chadi
  • Savepoint qulflarni ochmaydi — ROLLBACK TO dan keyin ham o'sha qatorlar qulflangan qoladi
  • Bir xil nomli yangi savepoint eskisini almashtiradi
  • DDL (ALTER, CREATE) baribir butun tranzaksiyani yashirin COMMIT qiladi

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 belgilash
  • ROLLBACK TO SAVEPOINT nom — nuqtadan keyingilarini bekor qilish, tranzaksiya davom etadi
  • RELEASE SAVEPOINT nom — nuqtani o'chirish
  • Import: har bir qator uchun savepoint — xatolarni tashlab, qolganini saqlash
  • MySQL da ichma-ich START TRANSACTION yo'q — ikkinchisi birinchisini COMMIT qiladi
  • Freymvorklarning ichma-ich transaction() — savepoint lar orqali
  • Savepoint qulflarni ochmaydi

Keyingi dars

40-dars: VIEW — yaratish, yangilash, o'chirish, qachon kerak