16-bo‘lim

Tranzaksiyalar

ACID xossalari, COMMIT va ROLLBACK, izolyatsiya darajalari, bloklash va deadlock.

🕑 13 daqiqa o‘qish 📄 612 so‘z 👁 5 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Klassik misol: pul o'tkazma
  2. ACID xossalari
  3. Asosiy buyruqlar
  4. Avtomatik tasdiqlash
  5. SAVEPOINT - oraliq nuqta
  6. Izolyatsiya darajalari
  7. Muammolar nima?
  8. Bloklash (locking)
  9. Deadlock
  10. Amaliy misol: buyurtma yaratish
  11. PHP da tranzaksiya
  12. Tranzaksiya qachon kerak?
  13. Xulosa

Tranzaksiya - bir nechta so'rovni bitta bo'linmas amal sifatida bajarish. U ma'lumot butunligini himoya qiladi.

Klassik misol: pul o'tkazma #

SQL
-- Husanboyning hisobidan Sardorga 500 000 so'm o'tkazish
UPDATE hisoblar SET balans = balans - 500000 WHERE id = 1;
-- <-- SHU YERDA SERVER O'CHSA NIMA BO'LADI?
UPDATE hisoblar SET balans = balans + 500000 WHERE id = 2;

Pul birinchi hisobdan yechildi, lekin ikkinchisiga tushmadi. 500 000 so'm yo'qoldi.

SQL
-- Tranzaksiya bilan
START TRANSACTION;

UPDATE hisoblar SET balans = balans - 500000 WHERE id = 1;
UPDATE hisoblar SET balans = balans + 500000 WHERE id = 2;

COMMIT;

Endi ikkala so'rov birga bajariladi yoki birga bekor qilinadi.

ACID xossalari #

A Atomarlik Hammasi yoki hech nima yarim bajarish yo'q C Izchillik Cheklovlar buzilmaydi FK, UNIQUE, CHECK I Izolyatsiya Tranzaksiyalar bir-biriga xalaqit bermaydi D Barqarorlik COMMIT dan keyin ma'lumot saqlanadi server o'chsa ham
ACID - ishonchli ma'lumotlar bazasining to'rt ustuni

Asosiy buyruqlar #

SQL
START TRANSACTION;      -- yoki BEGIN;

-- ... so'rovlar ...

COMMIT;                 -- tasdiqlash
-- yoki
ROLLBACK;               -- bekor qilish
SQL
-- Amaliy misol: xavfsiz yangilash
START TRANSACTION;

UPDATE mahsulotlar SET narx = narx * 1.15 WHERE kategoriya_id = 3;

SELECT nomi, narx FROM mahsulotlar WHERE kategoriya_id = 3;   -- tekshiramiz

-- Natija to'g'ri bo'lsa:
COMMIT;

-- Xato bo'lsa:
ROLLBACK;
Tranzaksiya - eng yaxshi himoya

Ishlab chiqarish serverida UPDATE yoki DELETE bajarayotganda har doim tranzaksiya ichida ishlang. SELECT bilan natijani tekshirib, keyin COMMIT yoki ROLLBACK qiling.

Bu odat sizni bir kun katta xatodan qutqaradi.

Avtomatik tasdiqlash #

MySQL da sukut bo'yicha autocommit yoqilgan - har bir so'rov darhol tasdiqlanadi.

SQL
SELECT @@autocommit;      -- 1 = yoqilgan

-- O'chirish
SET autocommit = 0;

UPDATE talabalar SET ortacha_baho = 5.0;
ROLLBACK;                 -- endi bekor qilish ishlaydi

-- Qayta yoqish
SET autocommit = 1;

START TRANSACTION yozganingizda autocommit vaqtincha o'chadi.

SAVEPOINT - oraliq nuqta #

SQL
START TRANSACTION;

INSERT INTO mijozlar (ism, email) VALUES ('Husanboy', '[email protected]');
SAVEPOINT mijoz_qoshildi;

INSERT INTO buyurtmalar (mijoz_id, jami) VALUES (LAST_INSERT_ID(), 450000);
SAVEPOINT buyurtma_qoshildi;

INSERT INTO buyurtma_elementlari (buyurtma_id, mahsulot_id, soni, narx)
VALUES (LAST_INSERT_ID(), 999, 1, 100000);      -- xato: mahsulot yo'q

ROLLBACK TO buyurtma_qoshildi;      -- faqat oxirgi amalni bekor qilamiz

COMMIT;                              -- mijoz va buyurtma saqlanadi

Izolyatsiya darajalari #

Bir vaqtda ishlayotgan tranzaksiyalar bir-birini qanchalik "ko'radi"?

DarajaIflos o'qishTakrorlanmas o'qishFantom qator
READ UNCOMMITTEDHaHaHa
READ COMMITTEDYo'qHaHa
REPEATABLE READ (MySQL standarti)Yo'qYo'qYo'q*
SERIALIZABLEYo'qYo'qYo'q
SQL
-- Joriy daraja
SELECT @@transaction_isolation;

-- O'zgartirish
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

Muammolar nima? #

Iflos o'qish (dirty read) - tasdiqlanmagan ma'lumotni o'qish:

Natija
Tranzaksiya A                    Tranzaksiya B
-------------------------------  -------------------------------
START TRANSACTION;
UPDATE hisoblar SET balans=0
    WHERE id=1;
                                 SELECT balans FROM hisoblar
                                     WHERE id=1;   -- 0 ko'radi!
ROLLBACK;                        -- lekin bu qiymat yo'q edi

Takrorlanmas o'qish (non-repeatable read) - bir tranzaksiya ichida bir xil so'rov turli natija beradi:

Natija
Tranzaksiya A                    Tranzaksiya B
-------------------------------  -------------------------------
SELECT balans ... ;  -- 100000
                                 UPDATE ... SET balans=50000;
                                 COMMIT;
SELECT balans ... ;  -- 50000!  -- boshqa natija

Fantom qator (phantom read) - yangi qatorlar paydo bo'ladi:

Natija
Tranzaksiya A                    Tranzaksiya B
-------------------------------  -------------------------------
SELECT COUNT(*) ... ;  -- 10
                                 INSERT INTO ... ;
                                 COMMIT;
SELECT COUNT(*) ... ;  -- 11!
Amalda standart darajani o'zgartirmang

MySQL ning standart REPEATABLE READ darajasi loyihalarning 99% i uchun to'g'ri tanlov. Uni faqat aniq muammoni hal qilish uchun va nima qilayotganingizni bilgan holda o'zgartiring.

READ UNCOMMITTED deyarli hech qachon ishlatilmaydi.

Bloklash (locking) #

SQL
-- O'qish uchun bloklash (boshqalar o'qiy oladi, o'zgartira olmaydi)
START TRANSACTION;
SELECT * FROM mahsulotlar WHERE id = 1 LOCK IN SHARE MODE;
COMMIT;

-- Yozish uchun bloklash (boshqalar umuman tega olmaydi)
START TRANSACTION;
SELECT ombor FROM mahsulotlar WHERE id = 1 FOR UPDATE;

UPDATE mahsulotlar SET ombor = ombor - 1 WHERE id = 1;
COMMIT;
SELECT ... FOR UPDATE qachon kerak?

Klassik masala - ombordagi oxirgi mahsulot:

Natija
Foydalanuvchi A                  Foydalanuvchi B
-------------------------------  -------------------------------
SELECT ombor;  -- 1 ta bor
                                 SELECT ombor;  -- 1 ta bor
UPDATE ombor = 0;
                                 UPDATE ombor = -1;   -- MUAMMO!

FOR UPDATE bilan B ning SELECT i A tugaguncha kutadi:

SQL
START TRANSACTION;
SELECT ombor FROM mahsulotlar WHERE id = 1 FOR UPDATE;
-- endi bu qator bloklangan
UPDATE mahsulotlar SET ombor = ombor - 1 WHERE id = 1;
COMMIT;

Deadlock #

Ikki tranzaksiya bir-birini kutib qolishi mumkin:

Natija
Tranzaksiya A                    Tranzaksiya B
-------------------------------  -------------------------------
UPDATE hisoblar WHERE id=1;      UPDATE hisoblar WHERE id=2;
    (1-qator bloklandi)              (2-qator bloklandi)
UPDATE hisoblar WHERE id=2;      UPDATE hisoblar WHERE id=1;
    (B ni kutadi)                    (A ni kutadi)
                DEADLOCK
Natija
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
Deadlock dan qochish
  1. Qatorlarga bir xil tartibda murojaat qiling (masalan, id bo'yicha o'sish tartibida)
  2. Tranzaksiyalarni qisqa qiling
  3. Tranzaksiya ichida foydalanuvchi javobini kutmang
  4. Deadlock bo'lsa - avtomatik qayta urinish mexanizmini yozing
PHP
$urinishlar = 0;
while ($urinishlar < 3) {
    try {
        $pdo->beginTransaction();
        // ... amallar ...
        $pdo->commit();
        break;
    } catch (PDOException $e) {
        $pdo->rollBack();
        if ($e->getCode() === '40001') {
            $urinishlar++;
            usleep(100000);      // 100 ms kutamiz
            continue;
        }
        throw $e;
    }
}
SQL
-- Oxirgi deadlock haqida ma'lumot
SHOW ENGINE INNODB STATUS\G

-- Hozirgi bloklashlar
SELECT * FROM performance_schema.data_locks;

Amaliy misol: buyurtma yaratish #

SQL
DELIMITER //

CREATE PROCEDURE buyurtma_yarat(
    IN p_mijoz_id INT UNSIGNED,
    IN p_mahsulot_id INT UNSIGNED,
    IN p_soni SMALLINT UNSIGNED
)
BEGIN
    DECLARE v_narx DECIMAL(12,2);
    DECLARE v_ombor INT;
    DECLARE v_buyurtma_id INT UNSIGNED;

    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;

    START TRANSACTION;

    -- Mahsulotni bloklaymiz
    SELECT narx, ombor INTO v_narx, v_ombor
    FROM mahsulotlar
    WHERE id = p_mahsulot_id
    FOR UPDATE;

    IF v_ombor < p_soni THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Omborda yetarli mahsulot yo''q';
    END IF;

    -- Buyurtma yaratamiz
    INSERT INTO buyurtmalar (mijoz_id, jami)
    VALUES (p_mijoz_id, v_narx * p_soni);

    SET v_buyurtma_id = LAST_INSERT_ID();

    -- Element qo'shamiz
    INSERT INTO buyurtma_elementlari (buyurtma_id, mahsulot_id, soni, narx)
    VALUES (v_buyurtma_id, p_mahsulot_id, p_soni, v_narx);

    -- Omborni kamaytiramiz
    UPDATE mahsulotlar SET ombor = ombor - p_soni WHERE id = p_mahsulot_id;

    COMMIT;
END //

DELIMITER ;

CALL buyurtma_yarat(1, 5, 2);

PHP da tranzaksiya #

PHP
$pdo->beginTransaction();

try {
    $pdo->prepare('UPDATE hisoblar SET balans = balans - ? WHERE id = ?')
        ->execute([500000, 1]);

    $pdo->prepare('UPDATE hisoblar SET balans = balans + ? WHERE id = ?')
        ->execute([500000, 2]);

    $pdo->commit();
} catch (Throwable $e) {
    $pdo->rollBack();
    throw $e;
}
MyISAM tranzaksiyani qo'llab-quvvatlamaydi
SQL
CREATE TABLE t (...) ENGINE=MyISAM;

START TRANSACTION;
UPDATE t SET ...;
ROLLBACK;                -- HECH NARSA QILMAYDI, ma'lumot o'zgargan

START TRANSACTION xato bermaydi, lekin ROLLBACK ishlamaydi. Har doim ENGINE=InnoDB ishlating.

Tranzaksiya qachon kerak? #

VaziyatTranzaksiya kerakmi
Bitta SELECTYo'q
Bitta INSERTYo'q
Pul o'tkazmaHa
Buyurtma yaratish (bir necha jadval)Ha
Ombor kamaytirishHa
Ommaviy importHa (tezlik uchun ham)
Xavfli UPDATE sinoviHa
Tranzaksiya tezlikni ham oshiradi
SQL
-- Sekin: har bir INSERT alohida diskka yoziladi
INSERT INTO loglar VALUES (...);   -- 10000 marta

-- Tez: hammasi bitta diskka yozish
START TRANSACTION;
INSERT INTO loglar VALUES (...);   -- 10000 marta
COMMIT;

Farq 10-50 barobar bo'lishi mumkin.

Amaliy topshiriq
  1. hisoblar jadvalini yarating (id, egasi, balans) va ikkita hisob qo'shing.
  2. Tranzaksiya bilan pul o'tkazish so'rovini yozing.
  3. O'rtada ROLLBACK qilib, balanslar o'zgarmaganini tekshiring.
  4. COMMIT qilib, o'zgarganini tekshiring.
  5. SAVEPOINT bilan uch bosqichli tranzaksiya yozing va o'rtadagisini bekor qiling.
  6. Ikki terminal ochib, birida FOR UPDATE bilan qatorni bloklang, ikkinchisida o'zgartirishga urinib ko'ring - nima bo'lishini kuzating.
  7. Ataylab deadlock hosil qiling (ikki terminalda teskari tartibda UPDATE).
  8. SET autocommit = 0 qilib, ROLLBACK ishlashini tekshiring.

Xulosa #

  • Tranzaksiya bir nechta so'rovni bitta bo'linmas amalga birlashtiradi.
  • ACID: Atomarlik, Izchillik, Izolyatsiya, Barqarorlik.
  • START TRANSACTION → so'rovlar → COMMIT yoki ROLLBACK.
  • Xavfli UPDATE/DELETE ni har doim tranzaksiya ichida sinang.
  • MySQL standart izolyatsiya darajasi - REPEATABLE READ, uni odatda o'zgartirmang.
  • SELECT ... FOR UPDATE bir vaqtda o'zgartirish muammosini hal qiladi.
  • Deadlock dan qochish uchun qatorlarga bir xil tartibda murojaat qiling.
  • Tranzaksiya faqat InnoDB da ishlaydi.

Keyingi bo'limda VIEW, protsedura va triggerlarni o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.