Transaction — ACID, BEGIN, COMMIT, ROLLBACK

~20 daq · MySQL · Transaction va Qulflar

Muammodan boshlaylik

Mijoz buyurtma berdi. Ilova uchta ishni bajaradi:

INSERT INTO buyurtmalar (mijoz_id) VALUES (2);                        -- 1. buyurtma
INSERT INTO buyurtma_qatorlari (...) VALUES (..., 5, 1, 3100000);     -- 2. qator
UPDATE mahsulotlar SET soni = soni - 1 WHERE id = 5;                  -- 3. ombordan ayirish

2-qadamdan keyin server o'chib qoldi (yoki xato chiqdi). Natija:

  • Buyurtma bor, qatori bor — lekin ombordan ayirilmagan
  • Yoki: buyurtma yaratildi, lekin qatorlari yo'q — "bo'sh" buyurtma

Baza yarim-tayyor, o'ziga zid holatda qoldi. Bank o'tkazmasida bu yanada dahshatli: bir hisobdan pul yechildi, ikkinchisiga tushmadi.

Yechim — tranzaksiya: bir nechta amalni bitta bo'linmas birlik qilish. Yo hammasi bajariladi, yo hech biri.


Asosiy sintaksis

START TRANSACTION;          -- yoki BEGIN;

INSERT INTO buyurtmalar (mijoz_id) VALUES (2);
SET @bid = LAST_INSERT_ID();
INSERT INTO buyurtma_qatorlari (buyurtma_id, mahsulot_id, soni, narx)
    VALUES (@bid, 5, 1, 3100000);
UPDATE mahsulotlar SET soni = soni - 1 WHERE id = 5 AND soni >= 1;

COMMIT;                     -- hammasini saqlash

Xato bo'lsa:

ROLLBACK;                   -- hammasini bekor qilish — go'yo hech narsa bo'lmagandek
  • START TRANSACTION va COMMIT orasidagi o'zgarishlar boshqa ulanishlarga ko'rinmaydi
  • COMMIT — barcha o'zgarishlar bir lahzada doimiy bo'ladi
  • ROLLBACK — barchasi bekor qilinadi
  • Ulanish uzilsa yoki server o'chsa — COMMIT qilinmagan tranzaksiya avtomatik ROLLBACK

Autocommit

Tranzaksiyasiz yozgan har bir so'rovingiz ham aslida tranzaksiya — avtomatik:

SELECT @@autocommit;   -- 1

autocommit = 1 (standart) — har bir alohida so'rov o'z tranzaksiyasida va darhol COMMIT. START TRANSACTION bu rejimni COMMIT/ROLLBACK gacha vaqtincha o'chiradi.

[!WARNING] Ba'zi vositalar (masalan, Workbench sozlamalarida yoki ba'zi kutubxonalarda) autocommit = 0 bo'lishi mumkin. Unda siz UPDATE qilasiz, natijani ko'rasiz — lekin boshqalar ko'rmaydi, va ulanishni yopsangiz o'zgarish yo'qoladi. "Saqladim, lekin saqlanmadi" muammosining keng tarqalgan sababi.


ACID — tranzaksiyaning to'rt kafolati

A — Atomicity (bo'linmaslik)

"Hammasi yoki hech narsa". Uchta amaldan ikkitasi bajarilib, uchinchisi bajarilmagan holat bo'lmaydi.

C — Consistency (izchillik)

Tranzaksiya bazani bir to'g'ri holatdan boshqa to'g'ri holatga o'tkazadi. Barcha cheklovlar (FOREIGN KEY, UNIQUE, CHECK, NOT NULL) tranzaksiya oxirida bajarilgan bo'ladi. Biror cheklov buzilsa — tranzaksiya rad etiladi.

I — Isolation (alohidalik)

Bir vaqtda ishlayotgan tranzaksiyalar bir-biriga xalaqit bermaydi — go'yo navbat bilan bajarilgandek. Bir tranzaksiyaning yarim-tayyor o'zgarishlari boshqasiga ko'rinmaydi. (Darajalari — 37-dars.)

D — Durability (doimiylik)

COMMIT dan keyin ma'lumot saqlanadi — hatto keyingi soniyada quvvat o'chsa ham. InnoDB COMMIT paytida o'zgarishni avval diskdagi jurnalga (redo log) yozadi, keyin "tayyor" deydi.

Harf Kafolat Busiz nima bo'ladi
A Hammasi yoki hech narsa Yarim-tayyor buyurtmalar
C Qoidalar buzilmaydi Mavjud bo'lmagan mijozga buyurtma
I Parallel tranzaksiyalar xalaqit bermaydi Bitta mahsulot ikki kishiga sotiladi
D Saqlangan — saqlangan COMMIT dan keyin ma'lumot yo'qoladi

Xatoni boshqarish

MySQL da xato chiqqanda tranzaksiya avtomatik bekor qilinmaydi — faqat xato bergan so'rov bekor qilinadi:

START TRANSACTION;
INSERT INTO buyurtmalar (mijoz_id) VALUES (2);       -- ✅
INSERT INTO buyurtmalar (mijoz_id) VALUES (9999);    -- ❌ foreign key xatosi
COMMIT;                                              -- birinchi INSERT saqlanadi!

Shuning uchun xato bo'lganda o'zingiz ROLLBACK qilishingiz kerak. Bu ilova kodida qilinadi:

<?php
// PHP + PDO
$pdo->beginTransaction();
try {
    $pdo->prepare('INSERT INTO buyurtmalar (mijoz_id) VALUES (?)')->execute([$mijozId]);
    $bid = $pdo->lastInsertId();

    $pdo->prepare('INSERT INTO buyurtma_qatorlari (buyurtma_id, mahsulot_id, soni, narx) VALUES (?, ?, ?, ?)')
        ->execute([$bid, $mahsulotId, $soni, $narx]);

    $stmt = $pdo->prepare('UPDATE mahsulotlar SET soni = soni - ? WHERE id = ? AND soni >= ?');
    $stmt->execute([$soni, $mahsulotId, $soni]);
    if ($stmt->rowCount() === 0) {
        throw new RuntimeException('Omborda yetarli emas');
    }

    $pdo->commit();
} catch (Throwable $e) {
    $pdo->rollBack();
    throw $e;
}
// Node.js + mysql2
const conn = await pool.getConnection();
try {
  await conn.beginTransaction();
  // ... so'rovlar
  await conn.commit();
} catch (e) {
  await conn.rollback();
  throw e;
} finally {
  conn.release();
}

E'tibor bering: omborda yetarli bo'lmasa — bu SQL xatosi emas (UPDATE shunchaki 0 qatorni o'zgartiradi). Biznes qoidasi buzilganini ilova tekshiradi va ROLLBACK qiladi.


Nima tranzaksiyani yakunlaydi

COMMIT va ROLLBACK dan tashqari, ba'zi buyruqlar yashirin COMMIT qiladi (8-dars):

START TRANSACTION;
UPDATE mahsulotlar SET narx = 1 WHERE id = 1;
ALTER TABLE mahsulotlar ADD COLUMN x INT;   -- ← yashirin COMMIT!
ROLLBACK;                                    -- UPDATE qaytmaydi

CREATE, ALTER, DROP, TRUNCATE, RENAME, LOCK TABLES — tranzaksiya ichida ishlatmang.


Tranzaksiyalarni qisqa tuting

❌ START TRANSACTION
   → mahsulotni o'qish
   → to'lov tizimiga HTTP so'rov (3 soniya)
   → email yuborish (2 soniya)
   → buyurtmani yozish
   COMMIT

Tranzaksiya ochiq turgan har bir soniya — qulflar ushlab turiladi (38-dars), boshqa so'rovlar kutadi. Sekin tashqi amallarni (HTTP, email, fayl) tranzaksiyadan tashqarida bajaring:

✅ to'lov tizimiga so'rov
   START TRANSACTION
   → buyurtmani yozish, ombordan ayirish
   COMMIT
   → email yuborish (yoki navbatga qo'yish)

Qaysi jadvallar tranzaksiyani qo'llab-quvvatlaydi

Faqat InnoDB (MySQL 8 da standart). Eski MyISAM jadvallari tranzaksiyani qo'llab-quvvatlamaydi — ROLLBACK ularga ta'sir qilmaydi, jimgina.

SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'dokon';

Eski loyihani olsangiz va ENGINE = MyISAM ko'rsangiz:

ALTER TABLE eski_jadval ENGINE = InnoDB;

Nima uchun shunday?

Nima uchun xato chiqqanda MySQL butun tranzaksiyani o'zi bekor qilmaydi?

Chunki ba'zan xato kutilgan va qayta ishlanadigan bo'ladi. Masalan: INSERT dublikat xatosi berdi — ilova uni ushlab, UPDATE ga o'tadi va tranzaksiyani davom ettiradi. Agar MySQL har xatoda hammasini bekor qilsa, bunday mantiq imkonsiz bo'lardi. MySQL qarorni sizga qoldiradi: davom etish yoki ROLLBACK. (PostgreSQL boshqacha yo'l tanlagan: xatodan keyin tranzaksiya "buzilgan" holatga o'tadi va faqat ROLLBACK qabul qiladi.)


Xulosa

Eslab qoling:

  • START TRANSACTION ... COMMIT / ROLLBACK — hammasi yoki hech narsa
  • ACID: Atomicity, Consistency, Isolation, Durability
  • autocommit = 1 — har bir so'rov o'z-o'zidan tranzaksiya
  • Xato tranzaksiyani avtomatik bekor qilmaydi — ilovada try/catch + rollBack()
  • DDL (ALTER, CREATE, DROP) — yashirin COMMIT
  • Tranzaksiyani qisqa tuting; HTTP, email — tashqarida
  • Faqat InnoDB; MyISAM tranzaksiyani bilmaydi

Keyingi dars

37-dars: Isolation Levels — READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE