Locking — Row lock, Advisory lock, Deadlock
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