16-bo‘lim
Tranzaksiyalar
ACID xossalari, COMMIT va ROLLBACK, izolyatsiya darajalari, bloklash va deadlock.
Ushbu bo‘lim mundarijasi
Tranzaksiya - bir nechta so'rovni bitta bo'linmas amal sifatida bajarish. U ma'lumot butunligini himoya qiladi.
Klassik misol: pul o'tkazma #
-- 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.
-- 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 #
Asosiy buyruqlar #
START TRANSACTION; -- yoki BEGIN;
-- ... so'rovlar ...
COMMIT; -- tasdiqlash
-- yoki
ROLLBACK; -- bekor qilish
-- 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;
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.
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 #
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"?
| Daraja | Iflos o'qish | Takrorlanmas o'qish | Fantom qator |
|---|---|---|---|
READ UNCOMMITTED | Ha | Ha | Ha |
READ COMMITTED | Yo'q | Ha | Ha |
REPEATABLE READ (MySQL standarti) | Yo'q | Yo'q | Yo'q* |
SERIALIZABLE | Yo'q | Yo'q | Yo'q |
-- 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:
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:
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:
Tranzaksiya A Tranzaksiya B
------------------------------- -------------------------------
SELECT COUNT(*) ... ; -- 10
INSERT INTO ... ;
COMMIT;
SELECT COUNT(*) ... ; -- 11!
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) #
-- 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:
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:
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:
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
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
- Qatorlarga bir xil tartibda murojaat qiling (masalan,
idbo'yicha o'sish tartibida) - Tranzaksiyalarni qisqa qiling
- Tranzaksiya ichida foydalanuvchi javobini kutmang
- Deadlock bo'lsa - avtomatik qayta urinish mexanizmini yozing
$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;
}
}
-- Oxirgi deadlock haqida ma'lumot
SHOW ENGINE INNODB STATUS\G
-- Hozirgi bloklashlar
SELECT * FROM performance_schema.data_locks;
Amaliy misol: buyurtma yaratish #
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 #
$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-quvvatlamaydiCREATE 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? #
| Vaziyat | Tranzaksiya kerakmi |
|---|---|
Bitta SELECT | Yo'q |
Bitta INSERT | Yo'q |
| Pul o'tkazma | Ha |
| Buyurtma yaratish (bir necha jadval) | Ha |
| Ombor kamaytirish | Ha |
| Ommaviy import | Ha (tezlik uchun ham) |
Xavfli UPDATE sinovi | Ha |
-- 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.
hisoblarjadvalini yarating (id,egasi,balans) va ikkita hisob qo'shing.- Tranzaksiya bilan pul o'tkazish so'rovini yozing.
- O'rtada
ROLLBACKqilib, balanslar o'zgarmaganini tekshiring. COMMITqilib, o'zgarganini tekshiring.SAVEPOINTbilan uch bosqichli tranzaksiya yozing va o'rtadagisini bekor qiling.- Ikki terminal ochib, birida
FOR UPDATEbilan qatorni bloklang, ikkinchisida o'zgartirishga urinib ko'ring - nima bo'lishini kuzating. - Ataylab deadlock hosil qiling (ikki terminalda teskari tartibda
UPDATE). SET autocommit = 0qilib,ROLLBACKishlashini tekshiring.
Xulosa #
- Tranzaksiya bir nechta so'rovni bitta bo'linmas amalga birlashtiradi.
- ACID: Atomarlik, Izchillik, Izolyatsiya, Barqarorlik.
START TRANSACTION→ so'rovlar →COMMITyokiROLLBACK.- Xavfli
UPDATE/DELETEni har doim tranzaksiya ichida sinang. - MySQL standart izolyatsiya darajasi -
REPEATABLE READ, uni odatda o'zgartirmang. SELECT ... FOR UPDATEbir 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.