Locking — Row lock, Advisory lock, Deadlock

~20 daq · PostgreSQL · Transaction va Concurrency

Ikkita foydalanuvchi bir vaqtda bir xil mahsulotning oxirgi nusxasini sotib olmoqchi. Ikkalasi ham qoldiq borligini tekshirdi — bor ekan. Ikkalasi ham buyurtma berdi. Natijada salbiy qoldiq — manfiy inventar.

Bu muammoni hal qilish uchun Lock — ma'lumotni vaqtincha "band qilish" kerak.


Row Lock — SELECT FOR UPDATE

Qatorni o'qib, uni band qilish — boshqa tranzaksiya o'zgartirolmasin:

BEGIN;

SELECT * FROM mahsulotlar
WHERE id = 1
FOR UPDATE;  -- ← qator band qilindi

-- Endi boshqa tranzaksiya bu qatorga tegolmaydi
UPDATE mahsulotlar SET qoldiq = qoldiq - 1 WHERE id = 1;

COMMIT; -- ← band qilingan qator ozod bo'ldi

Inventar muammosi yechimi:

BEGIN;

SELECT qoldiq FROM mahsulotlar WHERE id = 1 FOR UPDATE;
-- Agar boshqa tranzaksiya shu qatorni ushlab tursa — bu yerda kutadi

-- qoldiq > 0 ekanini tekshirish
UPDATE mahsulotlar SET qoldiq = qoldiq - 1 WHERE id = 1;
INSERT INTO buyurtmalar ...;

COMMIT;

SKIP LOCKED — band qatorlarni o'tkazib yuborish

Job queue pattern — band bo'lgan qatorlarni kutmasdan keyingisiga o'tish:

SELECT * FROM vazifalar
WHERE holat = 'kutilmoqda'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED;

Ko'p ishchi (worker) bir vaqtda ishlayotganda har biri boshqa vazifani oladi.


Advisory Lock — dasturiy lock

Jadval yoki qatorga emas, ixtiyoriy raqamga lock:

-- Lock olish
SELECT pg_advisory_lock(12345);  -- 12345 — ixtiyoriy son (ID, hash)

-- Ishlash...

-- Lock qo'yib yuborish
SELECT pg_advisory_unlock(12345);

Foydalanish holati — faqat bitta jarayon ishlashi kerak (cron job, importer):

-- Agar lock ololmasa — false qaytaradi, kutmaydi
SELECT pg_try_advisory_lock(42);
-- true  → lock olindi, davom etish
-- false → boshqa jarayon ishlayapti, chiqish

Deadlock — o'zaro bloklash

Ikkita tranzaksiya bir-birini kutib qoladi:

T1: A qatorini band qildi, B qatorini kutmoqda
T2: B qatorini band qildi, A qatorini kutmoqda
→ ikkalasi ham sonsiz kutadi — DEADLOCK

PostgreSQL deadlock ni avtomatik aniqlaydi va birini bekor qiladi:

ERROR: deadlock detected
DETAIL: Process 1234 waits for ShareLock on transaction 5678;
        Process 5678 waits for ShareLock on transaction 1234.
HINT: See server log for query details.

Deadlock dan qochish

Har doim bir xil tartibda lock olish:

-- ✅ — har ikkala tranzaksiya ham avval kichik ID ni oladi
BEGIN;
SELECT * FROM hisoblar WHERE id = 1 FOR UPDATE; -- kichik
SELECT * FROM hisoblar WHERE id = 2 FOR UPDATE; -- katta
-- ...
COMMIT;

Tartib bir xil bo'lsa — deadlock bo'lmaydi.


Tez-tez uchraydigan xatolar

FOR UPDATE siz race condition:

-- ❌ — ikkita tranzaksiya ham 1 ni ko'radi, ikkalasi ham yangilaydi
SELECT qoldiq FROM mahsulotlar WHERE id = 1; -- qoldiq = 1
-- ... tekshirish ...
UPDATE mahsulotlar SET qoldiq = qoldiq - 1 WHERE id = 1;
-- ✅ — FOR UPDATE bilan birinchi tranzaksiya band qiladi, ikkinchisi kutadi
SELECT qoldiq FROM mahsulotlar WHERE id = 1 FOR UPDATE;

Tranzaksiya ichida uzoq kutish:

Lock ushlab, uzoq vaqt kutish boshqa so'rovlarni bloklaydi. Tranzaksiyani qisqa tutish kerak.


Xulosa

-- Row lock — qatorkni band qilish
SELECT ... FOR UPDATE;           -- update/delete uchun
SELECT ... FOR SHARE;            -- faqat o'qish uchun (boshqalar ham o'qiy oladi)
SELECT ... FOR UPDATE SKIP LOCKED; -- band bo'lganlarni o'tkazib yuborish

-- Advisory lock
SELECT pg_advisory_lock(id);        -- kutadi
SELECT pg_try_advisory_lock(id);    -- kutmaydi (true/false)
SELECT pg_advisory_unlock(id);      -- ozod qilish

-- Deadlock — bir xil tartibda lock olish bilan oldini olish

Keyingi dars

47-dars: MVCC — Multi-Version Concurrency Control, qanday ishlaydi