- Ç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,SAVEPOINTvə 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
COMMITuğ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ı
| Anomaliya | Nə baş verir |
|---|---|
| Çirkli oxuma | T1 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 oxuma | T1 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 oxuma | T1 şə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₀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 oxuma | Təkrarlanmayan oxuma | Fantom |
|---|---|---|---|
| READ UNCOMMITTED | mümkün | mümkün | mümkün |
| READ COMMITTED | yox | mümkün | mümkün |
| REPEATABLE READ | yox | yox | mümkün |
| SERIALIZABLE | yox | yox | yox |
| VBİS | Susmaya görə səviyyə | Mexanizm |
|---|---|---|
| PostgreSQL | READ COMMITTED | MVCC |
| MySQL (InnoDB) | REPEATABLE READ | MVCC + sətir kilidləri |
| SQL Server | READ COMMITTED | kilidlər (snapshot rejimi seçimlidir) |
| Oracle | READ COMMITTED | MVCC |
| SQLite | SERIALIZABLE | bü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.
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ərHəllini gizlət
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.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
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.
-- 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!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.
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ərHəllini gizlət
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.
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
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.
-- 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
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.
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 UPDATEvə 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.