Məzmuna keç
Educora
Universitet30 dəq19 / 22

Tranzaksiyalar və paralel iş

ACID-in necə təmin edildiyini, paralel tranzaksiyaların hansı anomaliyalar yaratdığını, təcrid səviyyələrini, kilidləri, MVCC-ni və dalana dirənmələri (deadlock) öyrən.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Çirkli oxuma, təkrarlanmayan oxuma, fantom oxuma və itirilmiş yeniləmə anomaliyalarını tanımaq
  • Təcrid səviyyəsini tapşırığa görə seçmək, kilidləmə və MVCC yanaşmalarını müqayisə etmək
  • Atomar UPDATE, SELECT ... FOR UPDATE, SAVEPOINT və təkrar cəhdlərlə təhlükəsiz kod yazmaq, dalana dirənmədən qaçmaq

Anbarda son 1 noutbuk qalıb. Eyni saniyədə iki alıcı «Al» düyməsini basır, iki server prosesi qalığı oxuyur, hər ikisi «1» görür və hər ikisi satışı təsdiqləyir. Nəticədə bir alıcı olmayan malın pulunu ödəyir. Bu, bir proqramın səhvi deyil — paralel işin təbii problemidir. Əvvəlki dərslərdə tranzaksiyanı «hamısı və ya heç biri» kimi tanıdıq. İndi daha çətin suala baxırıq: yüzlərlə tranzaksiya eyni vaxtda işləyəndə VBİS onları bir-birindən necə təcrid edir və bunun qiyməti nədir?

ACID necə təmin olunur

  • Atomarlıq və davamlılıq: VBİS dəyişikliyi əvvəlcə ardıcıl jurnala (WAL — write-ahead log) yazır və diskə məcburi köçürür, yalnız sonra COMMIT uğurlu sayılır. Qəzadan sonra jurnal üzrə təsdiqlənmiş işlər bərpa, yarımçıqlar isə ləğv olunur.
  • Uyğunluq: məhdudiyyətlər (CHECK, FOREIGN KEY, UNIQUE) hər əmrdən sonra və ya tranzaksiyanın sonunda yoxlanılır.
  • Təcridlik: kilidlər və ya məlumatın çoxversiyalı saxlanması (MVCC) — bu dərsin əsas mövzusu.

Paralel işin anomaliyaları

AnomaliyaNə baş verir
Çirkli oxumaT1 T2-nin hələ təsdiqlənməmiş dəyişikliyini görür; T2 ROLLBACK etsə, T1 heç vaxt mövcud olmamış məlumatla işləyib.
Təkrarlanmayan oxumaT1 eyni sətri iki dəfə oxuyur, arada T2 onu dəyişib təsdiqləyir — iki fərqli qiymət alınır.
Fantom oxumaT1 şərtə uyğun sətirləri iki dəfə sayır, arada T2 yeni uyğun sətir əlavə edir — «fantom» peyda olur.
İtirilmiş yeniləməT1 və T2 eyni qiyməti oxuyur, hər biri öz hesabladığını yazır — birinin dəyişikliyi izsiz yox olur.
s = s₀ − q₂ ≠ s₀ − q₁ − q₂
burada:
  • s₀hər iki tranzaksiyanın oxuduğu ilkin qalıq
  • q₁, q₂T1 və T2-nin satdığı miqdar
  • ssonuncu yazanın (T2) saxladığı nəticə

İtirilmiş yeniləmə: «oxu → proqramda hesabla → yaz» sxemində T1-in satışı cəmdən düşür.

Təcrid səviyyələri

Tam təcrid bahadır, ona görə SQL standartı dörd səviyyə müəyyən edir: hər növbəti səviyyə daha çox anomaliyanı qadağan edir, amma paralelliyi azaldır. Serializable ən güclüsüdür: nəticə tranzaksiyaları hansısa ardıcıllıqla bir-bir icra etməklə eyni olmalıdır.

SəviyyəÇirkli oxumaTəkrarlanmayan oxumaFantom
READ UNCOMMITTEDmümkünmümkünmümkün
READ COMMITTEDyoxmümkünmümkün
REPEATABLE READyoxyoxmümkün
SERIALIZABLEyoxyoxyox
Standartın minimum tələbləri. Real sistemlər çox vaxt daha güclüdür: PostgreSQL-də REPEATABLE READ fantomlara da yol vermir, READ UNCOMMITTED isə READ COMMITTED kimi işləyir.
VBİSSusmaya görə səviyyəMexanizm
PostgreSQLREAD COMMITTEDMVCC
MySQL (InnoDB)REPEATABLE READMVCC + sətir kilidləri
SQL ServerREAD COMMITTEDkilidlər (snapshot rejimi seçimlidir)
OracleREAD COMMITTEDMVCC
SQLiteSERIALIZABLEbütün baza faylı üçün bir yazan

Kilidlər və MVCC

Klassik yanaşma kilidlərdir: oxuyan paylaşılan (S), yazan müstəsna (X) kilid alır, X kilidi isə istənilən başqa kilidlə uyğun gəlmir. İki fazalı kilidləmədə (2PL) tranzaksiya əvvəlcə yalnız kilid alır, sonra yalnız buraxır — bu, serializable nəticəyə zəmanət verir, amma oxuyanlar yazanları gözləyir. MVCC (çoxversiyalı idarəetmə) sətrin bir neçə versiyasını saxlayır: hər tranzaksiya öz «şəklini» (snapshot) görür, ona görə oxuyanlar yazanları, yazanlar da oxuyanları bloklamır. Yalnız eyni sətri dəyişən iki yazan bir-birini gözləyir.

Nümunə 1: itirilmiş yeniləmənin qarşısını almaq

Tətbiq əvvəlcə SELECT stock FROM products WHERE id = 1 edir, proqramda stock - 1 hesablayır və UPDATE products SET stock = 7 WHERE id = 1 yazır. İki alıcı eyni anda alanda qalıq 8-dən 6-ya yox, 7-yə düşür. Bunu necə düzəltməli?

Həllini göstər
1) Hesablamanı VBİS-ə ver — atomar yeniləmə: UPDATE products SET stock = stock - 1 WHERE id = 1 AND stock >= 1. İkinci yazan birincinin X kilidini gözləyir, sonra təzə qiyməti görür.
2) Yenilənən sətirlərin sayını yoxla: 0-dırsa, mal qurtarıb və satış rədd edilir.
3) Mürəkkəb məntiq lazımdırsa, pessimist kilid: SELECT stock FROM products WHERE id = 1 FOR UPDATE sətri tranzaksiyanın sonuna qədər kilidləyir.
4) Optimist alternativ: version sütunu və UPDATE ... WHERE id = 1 AND version = 7; 0 sətir yenilənibsə, kimsə qabaqlayıb — oxu və yenidən cəhd et.
SQL
CREATE TEMP TABLE sales_log (product TEXT, sold INTEGER);

UPDATE products SET stock = stock - 1 WHERE name = 'Laptop' AND stock >= 1;
INSERT INTO sales_log VALUES ('Laptop', changes());

UPDATE products SET stock = stock - 1 WHERE name = 'Monitor' AND stock >= 1;
INSERT INTO sales_log VALUES ('Monitor', changes());

SELECT l.product, l.sold, p.stock AS stock_now
FROM sales_log AS l
JOIN products AS p ON p.name = l.product
ORDER BY l.product;
▸ Gözlənilən nəticə
product | sold | stock_now
Laptop | 1 | 7
Monitor | 0 | 0
SQLite-da changes() son əmrin dəyişdirdiyi sətirlərin sayını verir: anbarda olmayan Monitor üçün 0, qalıq isə mənfiyə düşmür.

Dalana dirənmə (deadlock)

T1 A sətrini kilidləyib B-ni gözləyir, T2 isə B-ni kilidləyib A-nı gözləyir — heç biri irəli gedə bilmir. VBİS gözləmə qrafında dövr axtarır, tapanda tranzaksiyalardan birini «qurban» seçib ləğv edir, digəri davam edir. Aşağıda iki paralel seansda PostgreSQL-in cavabı göstərilib.

SQL
-- session 1                                   -- session 2
BEGIN;                                          BEGIN;
UPDATE accounts SET balance = balance - 30
 WHERE id = 1;                                  UPDATE accounts SET balance = balance - 10
                                                 WHERE id = 2;
UPDATE accounts SET balance = balance + 30
 WHERE id = 2;   -- waits for session 2
                                                UPDATE accounts SET balance = balance + 10
                                                 WHERE id = 1;   -- cycle!
Gözlənilən nəticə
ERROR:  deadlock detected
DETAIL:  Process 4211 waits for ShareLock on transaction 918; blocked by process 4187.
Process 4187 waits for ShareLock on transaction 917; blocked by process 4211.
HINT:  See server log for query details.
Qurban seçilən seans xəta alır və onun tranzaksiyası ləğv olunur; tətbiq onu yenidən icra etməlidir.
Nümunə 2: köçürmələrdə dalana dirənməni aradan qaldırmaq

Bank tətbiqində transfer(from, to, amount) əvvəlcə from, sonra to hesabını yeniləyir. 1→2 və 2→1 köçürmələri eyni anda gələndə bəzən deadlock yaranır. Kodu necə dəyişməli?

Həllini göstər
1) Səbəb: iki tranzaksiya eyni resursları əks ardıcıllıqla kilidləyir — gözləmə qrafında dövr yaranır.
2) Həll: kilidləri həmişə eyni qaydada al — əvvəlcə kiçik id-li hesabı, sonra böyüyü: first = min(from, to), second = max(from, to).
3) Hər iki köçürmə indi 1 nömrəli hesabdan başlayır: ikincisi sadəcə birincinin bitməsini gözləyir, dövr mümkün deyil.
4) Əlavə olaraq: tranzaksiyanı qısa saxla, deadlock xətasında (PostgreSQL-də SQLSTATE 40P01) bir neçə dəfə təkrar cəhd et.

Uzun tranzaksiyanın içində bir addımı ləğv etmək lazımdırsa, SAVEPOINT işlədilir: ROLLBACK TO yalnız həmin nöqtədən sonrakı dəyişiklikləri geri qaytarır, qalanı COMMIT ilə təsdiqlənir. Aşağıda sifariş saxlanılır, «hədiyyə» sətri isə ləğv olunur.

SQL
BEGIN;
INSERT INTO orders (customer_id, product_id, quantity, order_date)
VALUES (5, 10, 1, '2025-08-01');
SAVEPOINT gift;
INSERT INTO orders (customer_id, product_id, quantity, order_date)
VALUES (5, 8, 1, '2025-08-01');
ROLLBACK TO gift;   -- undo only the gift
COMMIT;

SELECT id, product_id, quantity, order_date
FROM orders
WHERE customer_id = 5
ORDER BY id;
▸ Gözlənilən nəticə
id | product_id | quantity | order_date
6 | 7 | 2 | 2025-03-18
13 | 10 | 1 | 2025-08-01
Tapşırıq

Atomar şərti yeniləmə ilə 3 ədəd Chess set sat (yalnız qalıq kifayət edirsə), sonra eyni üsulla 1 ədəd Monitor satmağa cəhd et. Sonda hər iki məhsulun adını və qalığını ada görə sıralanmış göstər. Qalıq heç vaxt mənfi olmamalıdır.

Tapşırıq · SQL
-- sell 3 chess sets

-- try to sell 1 monitor

SELECT name, stock
FROM products
WHERE name IN ('Chess set', 'Monitor')
ORDER BY name;
▸ Gözlənilən nəticə
name | stock
Chess set | 15
Monitor | 0
Tapşırıq

12 nömrəli sifarişi bir tranzaksiyada ləğv et: əvvəlcə onun miqdarını məhsulun qalığına qaytar, sonra sifarişi sil və COMMIT et. Sonda 4 nömrəli məhsul üçün adı, qalığı (stock) və qalan sifarişlərin sayını (orders) göstər.

Tapşırıq · SQL
BEGIN;
-- 1) give the quantity of order 12 back to its product
-- 2) delete order 12
COMMIT;

SELECT p.name, p.stock,
       (SELECT COUNT(*) FROM orders AS o WHERE o.product_id = p.id) AS orders
FROM products AS p
WHERE p.id = 4;
▸ Gözlənilən nəticə
name | stock | orders
Notebook | 510 | 1

Əsas fikirlər

  • Atomarlıq və davamlılıq qabaqcadan yazılan jurnalla (WAL), təcridlik isə kilidlər və ya MVCC ilə təmin olunur.
  • Paralel anomaliyalar: çirkli, təkrarlanmayan və fantom oxuma, itirilmiş yeniləmə.
  • Təcrid səviyyəsi yüksəldikcə anomaliyalar azalır, paralellik də azalır; PostgreSQL-də susmaya görə READ COMMITTED, MySQL InnoDB-də REPEATABLE READ-dir.
  • İtirilmiş yeniləmədən atomar UPDATE ... SET x = x - 1 WHERE x >= 1, FOR UPDATE və ya versiya yoxlaması qoruyur.
  • Deadlock-u VBİS aşkar edib bir tranzaksiyanı ləğv edir; kilidləri eyni ardıcıllıqla al və təkrar cəhdi proqramlaşdır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Çirkli oxuma nədir?