Lock (Kilit/Blok)
Kilitler tüm tablonun ya da satırların değiştirilmesini engeller ve sadece okumaya(select) izin verir.update ve delete işlemleri satırı kilitli hale getirir ve başka bir oturumun (session) o
satırları değiştirmesine engel olur. rollback ya da commit edilince kilit açılır.
Satırlar kilitli iken okuma (select) işlemleri engellenmez (MVCC konusundan bahsi
geçmişti ).
Lock işlemleri otomatik olarak gerçekleşir ama gerek olursa manuel tetiklemek
mümkündür. Böylece istenen tablo ya da satır değişikliğe kapatılabilir.
Örnek olarak;
Adım 1
# Tablo oluşturulur. testdb=# CREATE TABLE ekip( TKN integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, --Kurumsal kimlik no NO text UNIQUE, isim text, maas numeric ); # Test için 3 satır veri girişi yapılır. testdb=# INSERT INTO ekip VALUES (1, '1001', 'Mehmed', 1000.00), (2, '2001', 'Ahmed', 100.00), (3, '2002', 'Ahmed', 900.00);
Adım 2
# Session A
testdb=# SELECT pg_backend_pid();
pg_backend_pid
----------------
4793
testdb=*# UPDATE ekip SET maas = maas + 200 WHERE TKN = 1;
# Backend prosess ID’si 4793 olan oturum yukarıdaki update
cümlesi ile TKN=1 olan satırı kitlemiş.
testdb=# SELECT locktype, mode, granted, pid, pg_blocking_
pids(pid) AS wait_for FROM pg_locks WHERE relation =
'ekip'::regclass;
locktype | mode | granted | pid | wait_for
----------+------------------+---------+------+----------
relation | RowExclusiveLock | t | 4793 | {}
# rollback ya da commit edilmedi.
Adım 3
testdb=# SELECT pg_backend_pid();
pg_backend_pid
----------------
4798
testdb=*# UPDATE ekip SET maas = maas + 125 WHERE TKN = 1;
# Aynı satır güncellenmeye çalışıldı. Session A’nın kilidi
henüz kalkmadığı için. Cursor askıda/beklemede kalır.
testdb=# SELECT locktype, mode, granted, pid, pg_blocking_
pids(pid) AS wait_for FROM pg_locks WHERE relation =
'ekip'::regclass;
locktype | mode | granted | pid | wait_for
----------+------------------+---------+------+----------
relation | RowExclusiveLock | t | 4793 | {}
relation | RowExclusiveLock | t | 4798 | {4793}
tuple | ExclusiveLock | t | 4798 | {4793}
# 4798 oturumu 4793 oturumunu beklemektedir.
Diğer izleme yöntemi;
testdb=# SELECT pid, wait_event_type, wait_event, pg_blocking_
pids(pid)
testdb-# FROM pg_stat_activity
testdb-# WHERE backend_type = ‘client backend’;
pid | wait_event_type | wait_event | pg_blocking_pids
------+-----------------+---------------+------------------
1586 | Client | ClientRead | {}
4793 | Client | ClientRead | {}
1587 | Client | ClientRead | {}
1588 | Client | ClientRead | {}
4798 | Lock | transactionid | {4793}
4805 | | | {}
5511 | Client | ClientRead | {}
(7 rows)
Daha anlaşılır bir çıktı için aşağıdaki sorgu kullanılabilir. Bu sorgu ile view oluşturarak
daha kullanışlı hale getirilebilir. Bloklayan bloklanan PID ve SQL bilgilerini görmek
mümkün.
testdb=# Create view as kilit SELECT blocked_locks.pid AS blocked_pid, blocked_activity.usename AS blocked_user, blocking_locks.pid AS blocking_pid, blocking_activity.usename AS blocking_user, blocked_activity.query AS blocked_statement, blocking_activity.query AS current_statement_in_blocking_ process FROM pg_catalog.pg_locks blocked_locks JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_ activity.pid = blocked_locks.pid JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks. database AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks. relation AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks. tuple AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_ locks.virtualxid AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_ locks.transactionid AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks. classid AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks. objid AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks. objsubid AND blocking_locks.pid != blocked_locks.pid JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_ activity.pid = blocking_locks.pid WHERE NOT blocked_locks.granted;
testdb=# select * from kilit ; -------------------------+--------------------------------------------- blocked_pid | 1021 blocked_user | postgres blocking_pid | 800 blocking_user | postgres blocked_statement | UPDATE ekip SET maas = maas + 200 WHERE TKN = 2; current_statement_in_blocking_process | UPDATE ekip SET maas = maas + 300 WHERE TKN = 2;
testdb=# SELECT blocked_locks.pid AS blocked_pid, blocked_activity.usename AS blocked_user, blocking_locks.pid AS blocking_pid, blocking_activity.usename AS blocking_user, blocked_activity.query AS blocked_statement, blocking_activity.query AS current_statement_in_blocking_ process, blocked_activity.application_name AS blocked_application, blocking_activity.application_name AS blocking_application FROM pg_catalog.pg_locks blocked_locks JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_ activity.pid = blocked_locks.pid JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks. DATABASE AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks. relation AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks. tuple AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_ locks.virtualxid AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_ locks.transactionid AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks. classid AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks. objid AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks. objsubid AND blocking_locks.pid != blocked_locks.pid JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_ activity.pid = blocking_locks.pid WHERE NOT blocked_locks.GRANTED; ---------------+---------------------------------------------- blocked_pid | 1021 blocked_user | postgres blocking_pid | 800 blocking_user | postgres blocked_statement | UPDATE ekip SET maas = maas + 200 WHERE TKN = 2; current_statement_in_blocking_process | UPDATE ekip SET maas = maas + 300 WHERE TKN = 2; blocked_application | psql blocking_application | psql
Manuel kilit (block/lock) tetikleme
Kilit işlemi sadece update ve delete işlemleri ile gerçekleşmez. for update ve
lock table komutları ile manuel olarak kilit işlemi gerçekleştirilebilir
SELECT xmin,xmax, txid_current(), * FROM ekip FOR UPDATE; xmin | xmax | txid_current | tkn | no | isim | maas -------+-------+--------------+-----+------+--------+--------- 46015 | 46035 | 46037 | 2 | 2001 | Ahmed | 100.00 46015 | 46036 | 46037 | 3 | 2002 | Ahmed | 900.00 46034 | 46035 | 46037 | 1 | 1001 | Mehmed | 1500.00
SELECT locktype, transactionid, virtualtransaction, pid,mode, granted, fastpath FROM pg_locks WHERE transactionid=46037 ; locktype|transactionid|virtualtransaction|pid|mode | granted | fastpath ---------------+---------------+--------------------+-----+---- transactionid | 46037 | 3/9 | 800 | ExclusiveLock | t | f (1 row) # where koşulu ile de kullanmak mümkün. SELECT * FROM tablo_ismi WHERE kolon_ismi=abcd FOR UPDATE;
BEGIN; LOCK TABLE tablo_ismi; -- “VACUUM FULL” ile aynı modda kilitler.
Not: ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATEdiğer lock modları için detaylara postgresql.org/docs/current/index.html
EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE, ACCESS
EXCLUSIVE
sitesinden erişilebilir.
vacuum full, index create/rebuild işlemleri de lock’a neden olur.
Örneğin lock olan bir tabloda vacuum full başlatılırsa, devam edebilmesi için
lock’ın kalkması gerekir.
unlock table gibi sadece lock işlemini sonlandırmak için bir komut yoktur.
Ya commit edildiğinde ya da vacuum vb. işlem tamamlandığında/sonlandırıldığında lock
kalkar.
Deadlock
2 oturum birbirini kilitlediğinde deadlock oluşur.
Her iki otutumda da otomatik commit kapalı iken \set AUTOCOMMIT off
# Session A testdb=# UPDATE ekip SET maas = maas + 200 WHERE TKN = 1; # Session B testdb=# UPDATE ekip SET maas = maas + 200 WHERE TKN = 2; # Session A testdb=# UPDATE ekip SET maas = maas + 300 WHERE TKN = 2; # Session B testdb=# UPDATE ekip SET maas = maas + 100 WHERE TKN = 1; ERROR: deadlock detected DETAIL: Process 1021 waits for ShareLock on transaction 46040; blocked by process 800. Process 800 waits for ShareLock on transaction 46041; blocked by process 1021. HINT: See server log for query details. CONTEXT: while updating tuple (0,28) in relation "ekip"
$PGDATA/log/postgresql_%a.log dosyasına düşen loglar.
ERROR: deadlock detected
user=postgres db=testdb pid=1021 line=3 trx=46041|DETAIL: Process 1021 waits for ShareLock
on transaction 46040; blocked by process 800.
Process 800 waits for ShareLock on transaction 46041; blocked by process 1021.
Process 1021: UPDATE ekip SET maas = maas + 100 WHERE TKN = 1;
Process 800: UPDATE ekip SET maas = maas + 300 WHERE TKN = 2;
HINT: See server log for query details.
user=postgres db=testdb pid=1021 line=5 trx=46041|CONTEXT: while updating tuple (0,28) in
relation “ekip”
user=postgres db=testdb pid=1021 line=6 trx=46041|STATEMENT: UPDATE ekip SET maas =
maas + 100 WHERE TKN = 1;
deadlock_timeout : Bir lock gerçekleştiğinde burada belirtilen süre kadar
bekledikten sonra deadlock olup olmadığını kontrol eder. Deadlock tespit edildiğinde
otomatik olarak rollback edilir.
Deadlock uygulama kurgusundan kaynaklanmaktadır. Fazla sayıda deadlock
performansı ve belki de uygulamanın işleyişini etkileyebilir. Bu yüzden tespit edilen
deadock’lar yazılım geliştiricilere bildirilmelidir.
lock_timeout : Max time out süresini milisecond cinsinden belirler.
Default değeri 0’dır yani kapalıdır.
Örneğin 10.000 olarak ayarlanırsa,
lock’lanan transaction 10 saniye sonra sonlandırılır
ve satırı lock’layan commit ya da rollback yapana kadar tekrar çalıştırmasına izin
verilmez.
log_lock_waits : Aktif edilirse deadlock_timeout’ta belirtilen süreden uzun
süren lock’ları log dosyasına yazar.
Bir lock gerçekleştiğinde uygulama beklemeye geçer ve ekranda kum saati belirir.
Kullanıcı, uygulamanın geç cevap verdiğini dolayısı ile performans sorunu olduğunu
düşünecektir. Yapılması gerken, lock’ları tespit edip uygulamacılarara raporlayıp gerekli
yerlere commit veya belirli bir süreden sonra rollback ekletmektir.
