Tarkibga o'tish

19 β€” Tranzaksiyalar

⬅️ Oldingi: 18 β€” ALTER, Constraint, Foreign Key Β· 🏠 README Β· Keyingi: 20 β€” Normalizatsiya β€” to'g'ri schema ➑️

Bu bobda: tranzaksiya nima va u bizni qanday falokatdan asrashini ("yo hammasi, yo hech narsa" tamoyili), START TRANSACTION, COMMIT va ROLLBACK buyruqlarini, autocommit rejimini, parallel ishlaganda FOR UPDATE qulfi qanday qutqarishini, deadlock degan "tiqilinch" nima ekanini va intervyularning sevimli savoli β€” ACID xossalarini o'rganamiz.


Muammo: yarim bajarilgan ish

Do'konda buyurtma ikki qadamdan iborat: (1) buyurtma yoziladi, (2) ombordan kamayadi. 1-qadam bo'ldi, 2-qadamda svet o'chdi. Natija: buyurtma bor, ombor kamaymagan β€” ma'lumot yolg'on.

Bank misoli battarroq: Aziz Malikaga 100 000 o'tkazdi. (1) Azizdan ayirildi, (2) Malikaga... server qulab tushdi. 100 000 g'oyib bo'ldi.

Bunday "yarim ish"ning eng xavfli tomoni β€” hech qanday xato xabari chiqmaydi. Baza ishlayveradi, dastur ishlayveradi, faqat raqamlar yolg'on gapiradi. Bunday nosozlikni oradan hafta o'tib topish β€” pichan g'aramidan igna izlash bilan barobar.

Pul o'tkazish: tranzaksiyasiz yarmida uzilsa pul g'oyib bo'ladi, tranzaksiya bilan hammasi bekor qilinadi

Yechim: tranzaksiya β€” "yo hammasi, yo hech narsa"

Tranzaksiya β€” bir nechta buyruqni bitta bo'linmas paketga bog'lash. Paket yoki to'liq bajariladi, yoki umuman bajarilmaydi β€” o'rtasi yo'q. (hisoblar jadvalini hali yaratmadik β€” bob oxiridagi 4-masalada sinov bazasida o'zingiz qurasiz, hozir g'oyaga e'tibor bering.)

START TRANSACTION;

UPDATE hisoblar SET balans = balans - 100000 WHERE egasi = 'Aziz';
UPDATE hisoblar SET balans = balans + 100000 WHERE egasi = 'Malika';

COMMIT;      -- ikkalasi ham muvaffaqiyatli β€” endi saqlansin

Agar orada xato bo'lsa:

ROLLBACK;    -- hammasi BEKOR, boshlang'ich holatga qaytadi

COMMIT'gacha o'zgarishlar "qoralama"da β€” boshqa foydalanuvchilar ko'rmaydi. Buni xat yozishga o'xshating: "yuborish" tugmasini bosmaguningizcha xatni xohlagancha tahrirlaysiz yoki butunlay o'chirib tashlaysiz. COMMIT β€” "yuborish", ROLLBACK β€” "o'chirib tashlash".

Tranzaksiya oqimi: START TRANSACTION dan COMMIT yoki ROLLBACK gacha

πŸ“Œ Tranzaksiyalar jadvalning InnoDB "dvigateli"da ishlaydi. MySQL 8 da har bir yangi jadval standart holatda InnoDB bo'lib yaratiladi, shuning uchun qo'shimcha hech narsa sozlash shart emas β€” jadvallaringiz allaqachon tayyor.

Qo'lda sinab ko'rish

USE dokon;

START TRANSACTION;
UPDATE mahsulotlar SET soni = 0 WHERE id = 1;
SELECT soni FROM mahsulotlar WHERE id = 1;   -- 0 ko'rinadi (sizga)
ROLLBACK;
SELECT soni FROM mahsulotlar WHERE id = 1;   -- eski qiymat qaytdi!

Eslatma: MySQL'da har bir alohida buyruq o'z-o'zidan kichik tranzaksiya β€” yozdingizmi, darhol saqlanadi. Bu rejim autocommit deyiladi. START TRANSACTION esa "to'xta, men o'zim COMMIT demagunimcha saqlama" degani: bir nechta buyruqni bitta paketga bog'laydi.

Yana ikkita muhim qoidani bilib qo'ying:

  • Xato avtomatik ROLLBACK qilmaydi. Tranzaksiya ichida bitta buyruq xato bersa (masalan, yo'q jadvalga INSERT), MySQL odatda faqat o'sha buyruqni rad etadi β€” tranzaksiya ochiq qolaveradi. COMMIT yoki ROLLBACK qarori sizda (buni 7-masalada o'zingiz sinaysiz).
  • DDL buyruqlar tranzaksiyani yopib yuboradi. CREATE TABLE, ALTER, DROP kabi buyruqlar tranzaksiya o'rtasida kelsa, MySQL avval ungacha qilingan ishlarni jimgina COMMIT qilib yuboradi β€” ROLLBACK'ka qaytib bo'lmaydi. Shuning uchun tranzaksiya ichida faqat ma'lumot bilan ishlang: INSERT, UPDATE, DELETE, SELECT.

Real misol: buyurtma rasmiylashtirish

START TRANSACTION;

INSERT INTO buyurtmalar (mijoz_id, sana, holat) VALUES (2, NOW(), 'yangi');
SET @buyurtma = LAST_INSERT_ID();

INSERT INTO buyurtma_qatorlari (buyurtma_id, mahsulot_id, soni, narx)
VALUES (@buyurtma, 3, 1, 2800000);

UPDATE mahsulotlar SET soni = soni - 1 WHERE id = 3 AND soni >= 1;

COMMIT;

@buyurtma β€” sessiya o'zgaruvchisi (ulanish ochiq turguncha yashaydi): birinchi INSERT bergan id'ni eslab qolib, ikkinchisida ishlatdik. AND soni >= 1 β€” omborda yo'q narsani sotib yubormaslik himoyasi.

Real dasturda COMMIT'dan oldin yana bir tekshiruv qilinadi: SELECT ROW_COUNT(); β€” oxirgi UPDATE nechta qatorni o'zgartirganini aytadi. 0 chiqdimi β€” demak shart bajarilmagan (ombor bo'sh ekan), butun buyurtmani ROLLBACK qilamiz; 1 bo'lsa β€” bemalol COMMIT.

Parallellik: FOR UPDATE qulfi

Omborda oxirgi bitta iPhone qoldi deylik (soni = 1). Ikki kassir BIR VAQTDA sotmoqchi: ikkalasi ham SELECT bilan qaraydi, ikkalasi ham soni = 1 ni ko'radi, "bor ekan!" deb ikkalasi ham sotadi β†’ soni = -1. Mijozlardan biriga yo'q telefonni sotib qo'ydik.

Muammo β€” SELECT bilan UPDATE orasidagi vaqtda: siz "qancha bor?" deb qaragan paytdan "kamaytir" degan paytgacha boshqa odam ham ulgurib qolishi mumkin. Yechim β€” qatorni o'qiyotgandayoq qulflab qo'yish:

START TRANSACTION;
SELECT soni FROM mahsulotlar WHERE id = 2 FOR UPDATE;
-- FOR UPDATE = bu qator QULFLANDI. 2-kassir xuddi shu joyda KUTIB TURADI,
-- toki biz COMMIT qilmagunimizcha. Keyin u yangi (kamaygan) sonni ko'radi.
UPDATE mahsulotlar SET soni = soni - 1 WHERE id = 2;
COMMIT;

πŸ“Œ FOR UPDATE faqat tranzaksiya ichida ma'noga ega: qulf COMMIT yoki ROLLBACK bo'lganda bo'shaydi. Shuning uchun tranzaksiyani iloji boricha qisqa tuting β€” qulf turgan paytda boshqalar navbatda kutib turadi.

Deadlock: ikki tranzaksiya bir-birini kutib qoladi

Qulf β€” kuchli qurol, lekin uning "yon ta'siri" bor. Tasavvur qiling: 1-tranzaksiya A qatorni qulflab olib, endi B qatorni so'rayapti. Xuddi shu payt 2-tranzaksiya B ni qulflab olib, A ni so'rayapti. Ikkalasi ham bir-birini kutadi β€” tor ko'chada ikki mashina yuzma-yuz kelib qolganday: hech biri orqaga yurmasa, ikkalasi ham abadiy qotib qoladi. Bu holat deadlock (o'zaro qulf) deyiladi.

Deadlock: ikki tranzaksiya bir-birining qulfini kutib qolgan holat

Yaxshi yangilik: MySQL buni o'zi payqaydi va "qurbon" sifatida bittasini tanlab ROLLBACK qilib yuboradi β€” o'sha tranzaksiya Deadlock found when trying to get lock xatosini oladi. Dastur bu xatoni ushlab, tranzaksiyani qaytadan urinishi kerak. Oldini olish retsepti esa oddiy: qatorlarni hamma joyda bir xil tartibda qulflang (masalan, doim kichik id'dan kattasiga qarab) va tranzaksiyani qisqa tuting.

ACID β€” bilib qo'ying (intervyu savoli)

Tranzaksiya haqida gap ochilsa, intervyuda albatta ACID so'raladi. Bu β€” ishonchli tranzaksiyaning 4 xossasi:

ACID: tranzaksiyaning to'rt xossasi β€” Atomicity, Consistency, Isolation, Durability

  • Atomicity (butunlik): yo hammasi, yo hech narsa β€” paket o'rtasidan bo'linmaydi. Pul o'tkazmaning "yarmi" bajarilib qolishi mumkin emas.
  • Consistency (izchillik): tranzaksiya bazani bir to'g'ri holatdan boshqa to'g'ri holatga o'tkazadi β€” qoidalar (constraint, FOREIGN KEY) hech qachon buzilmaydi.
  • Isolation (izolyatsiya): parallel tranzaksiyalar bir-biriga xalaqit qilmaydi β€” har biri o'zini "bazada yolg'iz ishlayapman" deb his qiladi.
  • Durability (mustahkamlik): COMMIT bo'ldimi β€” svet o'chsa ham, server qulab tushsa ham ma'lumot diskda saqlanib qolgan.

19-bob masalalari

  1. (dokon) Yuqoridagi "qo'lda sinash"ni bajaring: TRANSACTION β†’ UPDATE β†’ ROLLBACK β†’ tekshiring
  2. (dokon) Xuddi shuni COMMIT bilan: o'zgarish saqlanib qoldimi?
  3. (dokon) ROLLBACK'dan keyin ROLLBACK qilib ko'ring β€” nima bo'ladi? (hech nima, tranzaksiya allaqachon tugagan)
  4. (sinov) hisoblar jadvali yarating (egasi VARCHAR(100), balans DECIMAL), Aziz=500000, Malika=200000 kiriting
  5. (sinov) Azizdan Malikaga 100 000 o'tkazing β€” to'liq tranzaksiya bilan. Oxirida ikkala balansni tekshiring (jami 700 000 bo'lishi kerak!)
  6. (sinov) O'tkazma qiling, lekin COMMIT o'rniga ROLLBACK β€” balanslar joyidami?
  7. (sinov) Tranzaksiya oching, avval bitta UPDATE bajaring (masalan, Aziz balansiga 1 qo'shing), keyin ataylab xato buyruq yozing (mavjud bo'lmagan jadvalga INSERT) β€” xato chiqadi, lekin tranzaksiya ochiq qoladi. Endi ROLLBACK qiling. Birinchi UPDATE ham bekor bo'ldimi, tekshiring
  8. (dokon) "Buyurtma rasmiylashtirish" misolini o'zingiz uchun takrorlang: yangi buyurtma + 2 ta qator + ombor kamaytirish, hammasi bitta tranzaksiyada
  9. (dokon) UPDATE ... SET soni = soni - 1 WHERE id = 6 AND soni >= 1 β€” Acer (soni=0 yoki siz qilgan qiymat) uchun bajaring. ROW_COUNT() funksiyasi bilan nechta qator o'zgarganini tekshiring: SELECT ROW_COUNT(); β€” 0 chiqsa "sotib bo'lmadi" degani
  10. (kutubxona) Kitob berish tranzaksiyasi: ijaralarga INSERT + kitoblarda nusxa_soni - 1 (faqat nusxa bor bo'lsa!)
  11. (kutubxona) Kitob qaytarish tranzaksiyasi: ijaralarda qaytarilgan_sanani to'ldirish + kitoblarda nusxa_soni + 1
  12. (klinika) Qabul yozish tranzaksiyasi: qabullarga INSERT (sana NOW(), tolov shifokorning qabul_narxiga teng β€” avval SELECT bilan oling yoki subquery ishlating)
  13. FOR UPDATE'ni HIS QILISH (2 ta oyna kerak!): 2 ta terminal oching. 1-oynada: START TRANSACTION; SELECT ... FOR UPDATE. 2-oynada xuddi shu SELECT ... FOR UPDATE β€” u KUTIB qoladi! 1-oynada COMMIT qiling β€” 2-oyna davom etadi. Buni ko'rgan odam qulflarni unutmaydi
  14. 13-mashqni FOR UPDATE'siz takrorlang β€” 2-oyna kutmaydi, darrov o'qiydi. Farqni yozib oling
  15. (taksi) Safar yakunlash tranzaksiyasi: safarlarga INSERT + haydovchi reytingini yangilash (o'rtacha baho asosida)
  16. Savol-javob: nega SELECT'larga odatda tranzaksiya kerak emas? (o'zgartirmaydi). Qachon kerak? (izchil "surat" kerak bo'lganda)
  17. (sinov) Autocommit'ni his qiling: SET autocommit = 0; qiling, UPDATE bajaring, boshqa oynadan qarang (ko'rinmaydi), COMMIT qiling (ko'rinadi). Oxirida SET autocommit = 1; ga qaytaring
  18. (dokon) Tranzaksiya ichida 3 ta UPDATE, 2-sidan keyin SAVEPOINT nuqta1; qo'ying, 3-UPDATE'dan keyin ROLLBACK TO nuqta1; β€” faqat 3-si bekor bo'ladi. Sinang!
  19. O'ylang: ombor kamaytirishda nega WHERE soni >= 1 sharti qulfdan tashqari YANA kerak? (himoya qatlamlari)
  20. Yozing: o'z hayotingizdan 3 ta "tranzaksiya bo'lishi SHART" ssenariy (masalan: Click to'lov, aviabilet bron...)