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,COMMITvaROLLBACKbuyruqlarini, autocommit rejimini, parallel ishlagandaFOR UPDATEqulfi 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.
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:
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".
π 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,DROPkabi 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.
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:
- 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¶
- (dokon) Yuqoridagi "qo'lda sinash"ni bajaring: TRANSACTION β UPDATE β ROLLBACK β tekshiring
- (dokon) Xuddi shuni COMMIT bilan: o'zgarish saqlanib qoldimi?
- (dokon) ROLLBACK'dan keyin ROLLBACK qilib ko'ring β nima bo'ladi? (hech nima, tranzaksiya allaqachon tugagan)
- (sinov)
hisoblarjadvali yarating (egasi VARCHAR(100), balans DECIMAL), Aziz=500000, Malika=200000 kiriting - (sinov) Azizdan Malikaga 100 000 o'tkazing β to'liq tranzaksiya bilan. Oxirida ikkala balansni tekshiring (jami 700 000 bo'lishi kerak!)
- (sinov) O'tkazma qiling, lekin COMMIT o'rniga ROLLBACK β balanslar joyidami?
- (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
- (dokon) "Buyurtma rasmiylashtirish" misolini o'zingiz uchun takrorlang: yangi buyurtma + 2 ta qator + ombor kamaytirish, hammasi bitta tranzaksiyada
- (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 - (kutubxona) Kitob berish tranzaksiyasi:
ijaralarga INSERT +kitoblardanusxa_soni - 1(faqat nusxa bor bo'lsa!) - (kutubxona) Kitob qaytarish tranzaksiyasi:
ijaralardaqaytarilgan_sanani to'ldirish +kitoblardanusxa_soni + 1 - (klinika) Qabul yozish tranzaksiyasi:
qabullarga INSERT (sana NOW(), tolov shifokorningqabul_narxiga teng β avval SELECT bilan oling yoki subquery ishlating) - 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
- 13-mashqni FOR UPDATE'siz takrorlang β 2-oyna kutmaydi, darrov o'qiydi. Farqni yozib oling
- (taksi) Safar yakunlash tranzaksiyasi:
safarlarga INSERT + haydovchi reytingini yangilash (o'rtacha baho asosida) - Savol-javob: nega SELECT'larga odatda tranzaksiya kerak emas? (o'zgartirmaydi). Qachon kerak? (izchil "surat" kerak bo'lganda)
- (sinov) Autocommit'ni his qiling:
SET autocommit = 0;qiling, UPDATE bajaring, boshqa oynadan qarang (ko'rinmaydi), COMMIT qiling (ko'rinadi). OxiridaSET autocommit = 1;ga qaytaring - (dokon) Tranzaksiya ichida 3 ta UPDATE, 2-sidan keyin
SAVEPOINT nuqta1;qo'ying, 3-UPDATE'dan keyinROLLBACK TO nuqta1;β faqat 3-si bekor bo'ladi. Sinang! - O'ylang: ombor kamaytirishda nega
WHERE soni >= 1sharti qulfdan tashqari YANA kerak? (himoya qatlamlari) - Yozing: o'z hayotingizdan 3 ta "tranzaksiya bo'lishi SHART" ssenariy (masalan: Click to'lov, aviabilet bron...)