Kunci PostgreSQL: 2. Kunci string

Terakhir kali, kami berbicara tentang kunci tingkat objek , khususnya, tentang kunci pada hubungan. Hari ini kita akan melihat bagaimana kunci baris diatur dalam PostgreSQL dan bagaimana mereka digunakan bersama dengan kunci objek, mari kita bicara tentang antrian menunggu dan mereka yang memanjat keluar dari belokan.



Kunci baris


Perangkat


Biarkan saya mengingatkan Anda pada beberapa kesimpulan penting dari artikel terakhir.

  • Kunci harus ada di suatu tempat di memori bersama server.
  • Semakin tinggi rincian kunci, semakin sedikit kompetisi (pertengkaran) di antara proses yang berjalan secara bersamaan.
  • Di sisi lain, semakin tinggi rinciannya, semakin banyak ruang memori ditempati oleh kunci.

Kami tentu menginginkan perubahan satu baris untuk tidak memblokir baris lain dari tabel yang sama. Tetapi kita tidak mampu memulai setiap baris dengan kunci kita sendiri.

Ada berbagai cara untuk mengatasi masalah ini. Dalam beberapa DBMS, ada peningkatan tingkat penguncian: jika ada terlalu banyak kunci tingkat baris, mereka digantikan oleh satu kunci yang lebih umum (misalnya, tingkat halaman atau seluruh tabel).

Seperti yang akan kita lihat nanti, PostgreSQL juga menggunakan mekanisme ini, tetapi hanya untuk kunci predikat. Kunci garis berbeda.

Dalam PostgreSQL, informasi bahwa suatu baris dikunci disimpan hanya dan secara eksklusif dalam versi baris di dalam halaman data (dan bukan dalam RAM). Artinya, ini bukan blok sama sekali dalam arti biasa, tetapi hanya sebuah pertanda. Tanda ini sebenarnya adalah nomor transaksi xmax dikombinasikan dengan bit informasi tambahan; sedikit kemudian kita akan melihat secara detail bagaimana ini bekerja.

Nilai tambahnya adalah kita dapat memblokir sebanyak mungkin jalur tanpa menggunakan sumber daya apa pun.

Tetapi ada kekurangannya : karena informasi tentang kunci tidak disajikan dalam RAM, proses lain tidak dapat dilakukan. Dan tidak ada kemungkinan pemantauan (untuk menghitung kunci, Anda perlu membaca seluruh tabel).

Yah, pemantauan baik-baik saja, tetapi ada sesuatu yang harus dilakukan dengan antrian. Untuk melakukan ini, Anda masih harus menggunakan kunci "biasa". Jika kita perlu menunggu sampai baris dilepaskan, pada kenyataannya, kita harus menunggu sampai akhir transaksi pemblokiran - semua kunci dilepaskan ketika melakukan atau memutar kembali. Dan untuk ini, Anda dapat meminta nomor pemblokiran transaksi pemblokiran (yang, saya ingat, dipegang oleh transaksi itu sendiri dalam mode luar biasa). Dengan demikian, jumlah kunci yang digunakan sebanding dengan jumlah proses yang berjalan secara bersamaan, dan tidak dengan jumlah baris yang diubah.

Mode Luar Biasa


Total ada 4 mode di mana Anda dapat mengunci garis. Dari jumlah tersebut, dua mode mewakili kunci eksklusif yang hanya dapat dimiliki oleh satu transaksi dalam satu waktu.

  • Mode FOR UPDATE menyiratkan perubahan lengkap (atau penghapusan) dari suatu baris.
  • FOR NO KEY UPDATE mode - hanya mengubah bidang-bidang yang tidak termasuk dalam indeks unik (dengan kata lain, dengan perubahan seperti itu, semua kunci asing tetap tidak berubah).

Perintah UPDATE sendiri memilih mode kunci minimum yang sesuai; biasanya baris dikunci dalam mode TANPA KUNCI KUNCI.

Seperti yang Anda ingat , ketika menghapus atau mengubah baris, nomor versi transaksi saat ini ditulis dalam bidang xmax dari versi saat ini. Ini menunjukkan bahwa versi baris telah dihapus oleh transaksi ini. Jadi, nomor xmax yang sama digunakan sebagai tanda pemblokiran. Faktanya, jika xmax dalam versi baris sesuai dengan transaksi aktif (belum selesai) dan kami ingin memperbarui jalur khusus ini, maka kami harus menunggu transaksi selesai, sehingga tidak diperlukan tanda tambahan.

Ayo lihat. Buat tabel akun, sama seperti di artikel sebelumnya.

=> CREATE TABLE accounts( acc_no integer PRIMARY KEY, amount numeric ); => INSERT INTO accounts VALUES (1, 100.00), (2, 200.00), (3, 300.00); 

Untuk melihat halaman-halamannya, tentu saja, kita perlu ekstensi pagepage yang sudah akrab.

 => CREATE EXTENSION pageinspect; 

Untuk kenyamanan, buat tampilan yang hanya menampilkan informasi yang kami minati: xmax dan beberapa bit informasi.

 => CREATE VIEW accounts_v AS SELECT '(0,'||lp||')' AS ctid, t_xmax as xmax, CASE WHEN (t_infomask & 128) > 0 THEN 't' END AS lock_only, CASE WHEN (t_infomask & 4096) > 0 THEN 't' END AS is_multi, CASE WHEN (t_infomask2 & 8192) > 0 THEN 't' END AS keys_upd, CASE WHEN (t_infomask & 16) > 0 THEN 't' END AS keyshr_lock, CASE WHEN (t_infomask & 16+64) = 16+64 THEN 't' END AS shr_lock FROM heap_page_items(get_raw_page('accounts',0)) ORDER BY lp; 

Jadi, kami memulai transaksi dan memperbarui jumlah akun pertama (kunci tidak berubah) dan jumlah akun kedua (kunci perubahan):

 => BEGIN; => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; => UPDATE accounts SET acc_no = 20 WHERE acc_no = 2; 

Kami melihat ke tampilan:

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 530492 | | | | | (0,2) | 530492 | | | t | | (2 rows) 

Mode kunci ditentukan oleh bit informasi keys_updated.

Kolom xmax yang sama juga digunakan saat mengunci baris dengan perintah SELECT FOR UPDATE, tetapi dalam kasus ini bit informasi tambahan (xmax_lock_only) diletakkan, yang menunjukkan bahwa versi baris hanya dikunci, tetapi tidak dihapus dan masih relevan.

 => ROLLBACK; => BEGIN; => SELECT * FROM accounts WHERE acc_no = 1 FOR NO KEY UPDATE; => SELECT * FROM accounts WHERE acc_no = 2 FOR UPDATE; 

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 530493 | t | | | | (0,2) | 530493 | t | | t | | (2 rows) 

 => ROLLBACK; 


Mode Bersama


Dua mode lagi mewakili kunci bersama yang dapat ditahan oleh beberapa transaksi.

  • Mode FOR SHARE digunakan ketika Anda perlu membaca sebuah string, tetapi Anda tidak boleh memperbolehkannya diubah dengan cara apa pun oleh transaksi lain.
  • Mode FOR KEY SHARE memungkinkan perubahan string, tetapi hanya bidang non-kunci. Mode ini, khususnya, secara otomatis digunakan oleh PostgreSQL saat memeriksa kunci asing.

Ayo lihat.

 => BEGIN; => SELECT * FROM accounts WHERE acc_no = 1 FOR KEY SHARE; => SELECT * FROM accounts WHERE acc_no = 2 FOR SHARE; 

Dalam versi baris kita melihat:

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 530494 | t | | | t | (0,2) | 530494 | t | | | t | t (2 rows) 

Dalam kedua kasus, bit keyshr_lock diatur, dan mode SHARE dapat dikenali dengan melihat satu bit informasi lagi.

Inilah yang tampak seperti matriks kompatibilitas mode umum.

modeUNTUK SHARE KUNCIUNTUK SAHAMTANPA PEMBARUAN KUNCIUNTUK PEMBARUAN
UNTUK SHARE KUNCIX
UNTUK SAHAMXX
TANPA PEMBARUAN KUNCIXXX
UNTUK PEMBARUANXXXX

Itu menunjukkan bahwa:

  • mode luar biasa saling bertentangan;
  • mode bersama kompatibel satu sama lain;
  • mode FOR KEY SHARE bersama yang dibagikan kompatibel dengan mode FOR FOR KEY UPDATE (yaitu, Anda dapat memperbarui bidang non-kunci secara bersamaan dan memastikan bahwa kunci tersebut tidak berubah).

Multi-transaksi


Sampai sekarang, kami berpikir bahwa kunci diwakili oleh jumlah transaksi pemblokiran di bidang xmax. Tetapi kunci bersama dapat disimpan oleh beberapa transaksi, dan beberapa nomor tidak dapat ditulis ke bidang xmax yang sama. Bagaimana menjadi

Untuk kunci bersama, yang disebut multi- transaksi (MultiXact) digunakan. Ini adalah grup transaksi yang diberi nomor terpisah. Nomor ini memiliki dimensi yang sama dengan nomor transaksi reguler, tetapi angka-angka tersebut dialokasikan secara independen (yaitu, sistem dapat memiliki nomor transaksi dan multi-transaksi yang sama). Untuk membedakan satu dari yang lain, bit informasi lain (xmax_is_multi) digunakan, dan informasi terperinci tentang anggota grup tersebut dan mode kunci terletak di file dalam direktori $ PGDATA / pg_multixact /. Secara alami, data yang terakhir digunakan disimpan dalam buffer dalam memori bersama server untuk akses yang lebih cepat.

Tambahkan ke kunci yang ada satu lagi yang luar biasa yang dieksekusi oleh transaksi lain (kita bisa melakukan ini, karena mode FOR KEY SHARE dan FOR NO KEY UPDATE kompatibel satu sama lain):

 | => BEGIN; | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 61 | | t | | | (0,2) | 530494 | t | | | t | t (2 rows) 

Di baris pertama, kita melihat bahwa nomor yang biasa telah digantikan oleh nomor multitransaksi - ini dibuktikan dengan bit xmax_is_multi.

Agar tidak mempelajari internal implementasi multitransaksi, Anda dapat menggunakan ekstensi lain yang memungkinkan Anda melihat semua informasi tentang semua jenis kunci baris dengan cara yang mudah.

 => CREATE EXTENSION pgrowlocks; => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]----------------------------- locked_row | (0,1) locker | 61 multi | t xids | {530494,530495} modes | {"Key Share","No Key Update"} pids | {5892,5928} -[ RECORD 2 ]----------------------------- locked_row | (0,2) locker | 530494 multi | f xids | {530494} modes | {"For Share"} pids | {5892} 

 => COMMIT; 

 | => ROLLBACK; 

Pengaturan beku


Karena angka-angka yang terpisah dialokasikan untuk multitransaksi, yang ditulis dalam bidang xmax versi baris, karena batas kapasitas digit penghitung, mereka mengalami masalah sampul xid yang sama dengan angka biasa.

Oleh karena itu, untuk nomor multi-transaksi, perlu juga melakukan analog pembekuan - ganti nomor lama dengan yang baru (atau dengan nomor transaksi reguler, jika pada saat pembekuan kunci dipegang oleh hanya satu transaksi).

Perhatikan bahwa pembekuan nomor transaksi biasa dilakukan hanya untuk bidang xmin (karena jika versi baris memiliki bidang xmax non-kosong, maka itu adalah versi yang tidak relevan dan akan dihapus, atau transaksi xmax dibatalkan dan jumlahnya tidak menarik bagi kami). Tetapi untuk multi-transaksi, kita berbicara tentang bidang xmax dari versi baris saat ini, yang dapat tetap relevan, tetapi itu terus-menerus diblokir oleh transaksi yang berbeda dalam mode bersama.

Untuk pembekuan transaksi multi, parameter yang mirip dengan parameter pembekuan biasa bertanggung jawab : vacuum_multixact_freeze_min_age , vacuum_multixact_freeze_table_age , autovacuum_multixact_freeze_max_age .

Siapa yang ekstrem?


Perlahan mendekati si manis. Mari kita lihat apa gambar kunci ketika beberapa transaksi akan memperbarui baris yang sama.

Mari kita mulai dengan membangun view di pg_locks. Pertama, kami akan membuat kesimpulan sedikit lebih kompak, dan kedua, kami akan membatasi diri kami pada kunci yang menarik (pada kenyataannya, kami membuang kunci nomor transaksi virtual, indeks pada tabel akun, pg_locks dan tampilan itu sendiri - secara umum, segala sesuatu yang tidak relevan dan hanya mengganggu).

 => CREATE VIEW locks_v AS SELECT pid, locktype, CASE locktype WHEN 'relation' THEN relation::regclass::text WHEN 'transactionid' THEN transactionid::text WHEN 'tuple' THEN relation::regclass::text||':'||tuple::text END AS lockid, mode, granted FROM pg_locks WHERE locktype in ('relation','transactionid','tuple') AND (locktype != 'relation' OR relation = 'accounts'::regclass); 

Sekarang mulailah transaksi pertama dan perbarui baris.

 => BEGIN; => SELECT txid_current(), pg_backend_pid(); 
  txid_current | pg_backend_pid --------------+---------------- 530497 | 5892 (1 row) 
 => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 
 UPDATE 1 

Bagaimana dengan kunci?

 => SELECT * FROM locks_v WHERE pid = 5892; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 5892 | relation | accounts | RowExclusiveLock | t 5892 | transactionid | 530497 | ExclusiveLock | t (2 rows) 

Transaksi memegang kunci meja dan sendiri. Sejauh ini semuanya diharapkan.

Kami memulai transaksi kedua dan mencoba memperbarui jalur yang sama.

 | => BEGIN; | => SELECT txid_current(), pg_backend_pid(); 
 | txid_current | pg_backend_pid | --------------+---------------- | 530498 | 5928 | (1 row) 
 | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

Bagaimana dengan kunci transaksi kedua?

 => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+------------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | transactionid | 530498 | ExclusiveLock | t 5928 | transactionid | 530497 | ShareLock | f 5928 | tuple | accounts:1 | ExclusiveLock | t (4 rows) 

Dan ini dia lebih menarik. Selain mengunci meja dan nomor sendiri, kita melihat dua kunci lagi. Transaksi kedua menemukan bahwa baris dikunci terlebih dahulu dan "digantung" menunggu nomornya (diberikan = f). Tetapi dari mana dan mengapa kunci versi baris (locktype = tuple) berasal?

Jangan bingung kunci versi baris (kunci tuple) dan kunci baris (kunci baris). Yang pertama adalah kunci tipe tuple biasa, yang terlihat di pg_locks. Yang kedua adalah tanda di halaman data: xmax dan bit informasi.

Ketika suatu transaksi hendak mengubah baris, ia melakukan urutan tindakan berikut:

  1. Menangkap kunci eksklusif pada versi string yang dapat diubah (tuple).
  2. Jika xmax dan bit informasi menunjukkan bahwa saluran terkunci, maka ia meminta untuk mengunci nomor transaksi xmax.
  3. Menentukan bit xmax dan informasi yang diperlukan.
  4. Merilis kunci versi baris.

Ketika baris diperbarui oleh transaksi pertama, baris itu juga meraih kunci versi baris (langkah 1), tetapi segera melepaskannya (langkah 4).

Ketika transaksi kedua tiba, dia menangkap kunci versi baris (item 1), tetapi terpaksa meminta kunci pada nomor transaksi pertama (item 2) dan menggantungnya.

Apa yang terjadi jika transaksi serupa yang ketiga muncul? Dia akan mencoba menangkap kunci versi baris (item 1) dan akan menggantung pada langkah ini. Lihat itu.

 || => BEGIN; || => SELECT txid_current(), pg_backend_pid(); 
 || txid_current | pg_backend_pid || --------------+---------------- || 530499 | 5964 || (1 row) 
 || => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

 => SELECT * FROM locks_v WHERE pid = 5964; 
  pid | locktype | lockid | mode | granted ------+---------------+------------+------------------+--------- 5964 | relation | accounts | RowExclusiveLock | t 5964 | tuple | accounts:1 | ExclusiveLock | f 5964 | transactionid | 530499 | ExclusiveLock | t (3 rows) 

Transaksi keempat, kelima, dll. Yang ingin memperbarui baris yang sama tidak akan berbeda dari transaksi 3 - semuanya akan "hang" pada kunci versi baris yang sama.

Tambahkan transaksi lain ke heap.

 ||| => BEGIN; ||| => SELECT txid_current(), pg_backend_pid(); 
 ||| txid_current | pg_backend_pid ||| --------------+---------------- ||| 530500 | 6000 ||| (1 row) 
 ||| => UPDATE accounts SET amount = amount - 100.00 WHERE acc_no = 1; 

 => SELECT * FROM locks_v WHERE pid = 6000; 
  pid | locktype | lockid | mode | granted ------+---------------+------------+------------------+--------- 6000 | relation | accounts | RowExclusiveLock | t 6000 | transactionid | 530500 | ExclusiveLock | t 6000 | tuple | accounts:1 | ExclusiveLock | f (3 rows) 

Gambaran umum dari harapan saat ini dapat dilihat pada tampilan pg_stat_activity, menambahkan informasi tentang proses pemblokiran:

 => SELECT pid, wait_event_type, wait_event, pg_blocking_pids(pid) FROM pg_stat_activity WHERE backend_type = 'client backend'; 
  pid | wait_event_type | wait_event | pg_blocking_pids ------+-----------------+---------------+------------------ 5892 | | | {} 5928 | Lock | transactionid | {5892} 5964 | Lock | tuple | {5928} 6000 | Lock | tuple | {5928,5964} (4 rows) 

Ternyata semacam "antrian", di mana ada yang pertama (orang yang memegang versi kunci dari string) dan semua yang lain berbaris di belakang yang pertama.

Mengapa kita membutuhkan desain yang begitu canggih? Misalkan kita tidak akan memiliki kunci versi untuk string. Kemudian transaksi kedua dan ketiga (dan seterusnya) akan menunggu pemblokiran jumlah transaksi pertama. Pada saat penyelesaian transaksi pertama, sumber daya yang diblokir menghilang ( dan apa yang Anda lakukan di sini, eh? Transaksi telah berakhir ) dan sekarang semuanya tergantung pada proses menunggu mana yang pertama kali dibangunkan oleh sistem operasi dan, karenanya, akan memiliki waktu untuk mengunci saluran. Semua proses lain juga akan dibangunkan, tetapi mereka harus mengantri lagi - sekarang setelah proses lain.

Ini penuh dengan fakta bahwa beberapa transaksi dapat menunggu tanpa batas untuk gilirannya jika, karena kombinasi keadaan yang tidak menguntungkan, ia akan selalu berkeliling transaksi lainnya. Dalam bahasa Inggris, situasi ini disebut kelaparan kunci.

Dalam kasus kami, ternyata hampir sama, tetapi masih sedikit lebih baik: transaksi yang datang di kedua dijamin bahwa itu akan mendapatkan akses ke sumber daya berikutnya. Tetapi apa yang terjadi pada yang berikut (ketiga dan seterusnya)?

Jika transaksi pertama berakhir dengan rollback, semuanya akan baik-baik saja: transaksi masuk akan berjalan sesuai urutan.

Tapi - ini nasib buruk - jika transaksi pertama selesai dengan komit, maka tidak hanya nomor transaksi menghilang, tetapi juga versi garis! Artinya, versi itu, tentu saja, tetap ada, tetapi tidak lagi relevan, dan perlu memperbarui versi terbaru yang sama sekali berbeda (dari baris yang sama). Sumber daya, yang pada gilirannya, menghilang, dan semua orang mengatur perlombaan untuk memiliki sumber daya baru.

Biarkan transaksi pertama selesai dengan komit.

 => COMMIT; 

Transaksi kedua akan dibangunkan dan menjalankan paragraf. 3 dan 4.

 | UPDATE 1 

 => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | transactionid | 530498 | ExclusiveLock | t (2 rows) 

Bagaimana dengan transaksi ketiga? Dia melewatkan langkah 1 (karena sumber daya telah hilang) dan terjebak pada langkah 2:

 => SELECT * FROM locks_v WHERE pid = 5964; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 5964 | relation | accounts | RowExclusiveLock | t 5964 | transactionid | 530498 | ShareLock | f 5964 | transactionid | 530499 | ExclusiveLock | t (3 rows) 

Dan hal yang sama terjadi pada transaksi keempat:

 => SELECT * FROM locks_v WHERE pid = 6000; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 6000 | relation | accounts | RowExclusiveLock | t 6000 | transactionid | 530498 | ShareLock | f 6000 | transactionid | 530500 | ExclusiveLock | t (3 rows) 

Artinya, transaksi ketiga dan keempat sedang menunggu penyelesaian transaksi kedua. Garis berubah menjadi kerumunan labu .

Kami menyelesaikan semua transaksi yang dimulai.

 | => COMMIT; 

 || UPDATE 1 
 || => COMMIT; 

 ||| UPDATE 1 
 ||| => COMMIT; 

Rincian lebih lanjut tentang memblokir string dapat ditemukan di README.tuplock .

Anda tidak berdiri di sini


Jadi, ide skema pemblokiran dua tingkat adalah untuk mengurangi kemungkinan penantian abadi untuk transaksi "nasib buruk". Namun demikian, seperti yang telah kita lihat, situasi seperti itu sangat mungkin terjadi. Dan jika aplikasi menggunakan kunci bersama, semuanya bisa menjadi lebih sedih.

Biarkan transaksi pertama mengunci baris dalam mode bersama.

 => BEGIN; => SELECT txid_current(), pg_backend_pid(); 
  txid_current | pg_backend_pid --------------+---------------- 530501 | 5892 (1 row) 
 => SELECT * FROM accounts WHERE acc_no = 1 FOR SHARE; 
  acc_no | amount --------+-------- 1 | 100.00 (1 row) 

Transaksi kedua mencoba memperbarui baris yang sama, tetapi tidak bisa - mode SHARE dan NO KEY UPDATE tidak kompatibel.

 | => BEGIN; | => SELECT txid_current(), pg_backend_pid(); 
 | txid_current | pg_backend_pid | --------------+---------------- | 530502 | 5928 | (1 row) 
 | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

Transaksi kedua menunggu penyelesaian yang pertama dan memegang kunci versi baris - untuk saat ini, semuanya seperti yang terakhir kali.

 => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+-------------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | tuple | accounts:10 | ExclusiveLock | t 5928 | transactionid | 530501 | ShareLock | f 5928 | transactionid | 530502 | ExclusiveLock | t (4 rows) 

Dan kemudian transaksi ketiga muncul yang menginginkan kunci bersama. Masalahnya adalah ia tidak mencoba menangkap kunci pada versi baris (karena tidak akan mengubah baris), tetapi hanya merangkak keluar dari belokan - itu kompatibel dengan transaksi pertama.

 || BEGIN || => SELECT txid_current(), pg_backend_pid(); 
 || txid_current | pg_backend_pid || --------------+---------------- || 530503 | 5964 || (1 row) 
 || => SELECT * FROM accounts WHERE acc_no = 1 FOR SHARE; 
 || acc_no | amount || --------+-------- || 1 | 100.00 || (1 row) 

Dan sekarang dua transaksi memblokir baris:

 => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]--------------- locked_row | (0,10) locker | 62 multi | t xids | {530501,530503} modes | {Share,Share} pids | {5892,5964} 

Apa yang terjadi sekarang ketika transaksi pertama selesai? Transaksi kedua akan dibangunkan, tetapi akan melihat bahwa kunci baris tidak menghilang di mana pun, dan akan kembali berdiri di "antrian" - kali ini untuk transaksi ketiga:

 => COMMIT; => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+-------------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | tuple | accounts:10 | ExclusiveLock | t 5928 | transactionid | 530503 | ShareLock | f 5928 | transactionid | 530502 | ExclusiveLock | t (4 rows) 

Dan hanya ketika transaksi ketiga selesai (dan jika tidak ada kunci bersama lainnya muncul selama waktu ini), yang kedua akan dapat melakukan pembaruan.

 || => COMMIT; 

 | UPDATE 1 
 | => ROLLBACK; 

Mungkin sudah waktunya untuk menarik beberapa kesimpulan praktis.

  • Memperbarui baris yang sama dalam sebuah tabel pada saat yang sama dalam banyak proses paralel bukanlah ide yang baik.
  • Jika Anda menggunakan kunci bersama tipe SHARE dalam aplikasi, maka diam-diam.
  • Memeriksa kunci asing tidak boleh mengganggu, karena bidang kunci biasanya tidak berubah, dan mode KEY SHARE dan NO KEY UPDATE kompatibel.


Diminta untuk tidak meminjam


Biasanya, perintah SQL berharap untuk mengeluarkan sumber daya yang mereka butuhkan. Tetapi terkadang Anda ingin menolak untuk mengeksekusi perintah jika kunci tidak dapat diperoleh dengan segera. Untuk melakukan ini, perintah seperti SELECT, LOCK, ALTER, memungkinkan Anda untuk menggunakan frase NOWAIT.

Sebagai contoh:

 => BEGIN; => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

 | => SELECT * FROM accounts FOR UPDATE NOWAIT; 
 | ERROR: could not obtain lock on row in relation "accounts" 

Perintah segera gagal jika sumber daya sibuk. Dalam kode aplikasi, kesalahan seperti itu dapat dicegat dan diproses.

Anda tidak dapat menentukan frasa NOWAIT untuk perintah UPDATE dan DELETE, tetapi Anda dapat terlebih dahulu menjalankan SELECT FOR UPDATE NOWAIT, dan kemudian, jika mungkin, perbarui atau hapus baris.

Ada pilihan lain untuk tidak menunggu - gunakan perintah SELECT FOR dengan frasa SKIP LOCKED. Perintah seperti itu akan melewati garis yang terkunci, tetapi memproses yang gratis.

 | => BEGIN; | => DECLARE c CURSOR FOR | SELECT * FROM accounts ORDER BY acc_no FOR UPDATE SKIP LOCKED; | => FETCH c; 
 | acc_no | amount | --------+-------- | 2 | 200.00 | (1 row) 

Dalam contoh ini, baris pertama - yang diblokir dilewati dan kami segera menerima (dan memblokir) yang kedua.

Dalam praktiknya, ini memungkinkan Anda untuk mengatur pemrosesan antrian multi-threaded. Anda seharusnya tidak membuat aplikasi lain untuk perintah ini - jika Anda ingin menggunakannya, kemungkinan besar Anda akan kehilangan beberapa solusi yang lebih sederhana.

 => ROLLBACK; 
 | => ROLLBACK; 

Untuk dilanjutkan .

Source: https://habr.com/ru/post/id463819/


All Articles