Bekerja dengan database, kontrol konkurensi adalah konsep yang memastikan bahwa transaksi database dilakukan secara bersamaan tanpa melanggar integritas data.
Ada banyak teori dan pendekatan berbeda seputar konsep ini dan cara mencapainya, tetapi kami akan merujuk secara singkat cara PostgreSQL dan MySQL (saat menggunakan InnoDB) menanganinya, dan masalah umum yang dapat muncul dalam sistem yang sangat bersamaan:kebuntuan.
Engine ini menerapkan kontrol konkurensi dengan menggunakan metode yang disebut MVCC (Multiversion Concurrency Control). Dalam metode ini, saat item diperbarui, perubahan tidak akan menimpa data asli, tetapi versi baru item (dengan perubahan) akan dibuat. Dengan demikian kita akan memiliki beberapa versi item yang disimpan.
Salah satu keuntungan utama dari model ini adalah bahwa kunci yang diperoleh untuk kueri (membaca) data tidak bertentangan dengan kunci yang diperoleh untuk menulis data, sehingga membaca tidak pernah memblokir penulisan, dan menulis tidak pernah memblokir membaca.
Namun, jika beberapa versi dari item yang sama disimpan, versi mana yang akan dilihat oleh transaksi? Untuk menjawab pertanyaan itu kita perlu meninjau kembali konsep isolasi transaksi. Transaksi menentukan tingkat isolasi, yang menentukan sejauh mana satu transaksi harus diisolasi dari sumber daya atau modifikasi data yang dibuat oleh transaksi lain. Derajat ini terkait langsung dengan penguncian yang dihasilkan oleh suatu transaksi, dan karena itu, karena dapat ditentukan pada tingkat transaksi, derajat ini dapat menentukan dampak transaksi yang sedang berjalan terhadap transaksi lain yang sedang berjalan.
Ini adalah topik yang sangat menarik dan panjang, meskipun kami tidak akan membahas terlalu banyak detail di blog ini. Kami akan merekomendasikan dokumentasi resmi PostgreSQL dan MySQL untuk bacaan lebih lanjut tentang topik ini.
Jadi, mengapa kita membahas topik di atas ketika berhadapan dengan kebuntuan? Karena perintah sql akan secara otomatis memperoleh kunci untuk memastikan perilaku MVCC, dan jenis kunci yang diperoleh bergantung pada isolasi transaksi yang ditentukan.
Ada beberapa jenis kunci (sekali lagi topik panjang dan menarik untuk ditinjau untuk PostgreSQL dan MySQL) tetapi, yang penting tentang mereka, adalah bagaimana mereka berinteraksi (paling tepatnya, bagaimana mereka bertentangan) satu sama lain. Mengapa demikian? Karena dua transaksi tidak dapat menahan kunci mode yang saling bertentangan pada objek yang sama secara bersamaan. Dan detail non-kecil, setelah diperoleh, kunci biasanya ditahan sampai akhir transaksi.
Ini adalah contoh PostgreSQL tentang bagaimana tipe penguncian saling bertentangan:
Konflik jenis penguncian PostgreSQLDan untuk MySQL:
Jenis penguncian MySQL konflikX=kunci eksklusif IX=niat kunci eksklusif
S=kunci bersama IS=niat kunci bersama
Jadi apa yang terjadi ketika saya memiliki dua transaksi yang sedang berjalan yang ingin menahan kunci yang bertentangan pada objek yang sama pada saat yang bersamaan? Salah satu dari mereka akan mendapatkan kunci dan yang lainnya harus menunggu.
Jadi sekarang kita berada dalam posisi untuk benar-benar memahami apa yang terjadi selama kebuntuan.
Lalu apa itu kebuntuan? Seperti yang dapat Anda bayangkan, ada beberapa definisi untuk kebuntuan database, tetapi saya suka yang berikut ini karena kesederhanaannya.
Kebuntuan basis data adalah situasi di mana dua atau lebih transaksi menunggu satu sama lain untuk melepaskan kunci.
Jadi misalnya, situasi berikut akan membawa kita ke jalan buntu:
Contoh kebuntuanDi sini, aplikasi A mendapatkan kunci pada tabel 1 baris 1 untuk melakukan pembaruan.
Pada saat yang sama aplikasi B mendapatkan kunci pada tabel 2 baris 2.
Sekarang aplikasi A perlu mendapatkan kunci pada tabel 2 baris 2, untuk melanjutkan eksekusi dan menyelesaikan transaksi, tetapi tidak dapat memperoleh kunci karena dipegang oleh aplikasi B. Aplikasi A perlu menunggu aplikasi B untuk melepaskannya .
Tetapi aplikasi B perlu mendapatkan kunci pada tabel 1 baris 1, untuk melanjutkan eksekusi dan menyelesaikan transaksi, tetapi tidak dapat memperoleh kunci karena dipegang oleh aplikasi A.
Jadi di sini kita berada dalam situasi kebuntuan. Aplikasi A menunggu resource yang dipegang oleh aplikasi B agar selesai dan aplikasi B menunggu resource yang dipegang oleh aplikasi A. Jadi, bagaimana melanjutkannya? Mesin basis data akan mendeteksi kebuntuan dan menghentikan salah satu transaksi, membuka blokir transaksi lainnya, dan memunculkan kesalahan kebuntuan pada transaksi yang dihentikan.
Mari kita periksa beberapa contoh kebuntuan PostgreSQL dan MySQL:
PostgreSQL
Misalkan kita memiliki database pengujian dengan informasi dari negara-negara di dunia.
world=# SELECT code,region,population FROM country WHERE code IN ('NLD','AUS');
code | region | population
------+---------------------------+------------
NLD | Western Europe | 15864000
AUS | Australia and New Zealand | 18886000
(2 rows)
Kami memiliki dua sesi yang ingin membuat perubahan pada database.
Sesi pertama akan mengubah bidang wilayah untuk kode NLD, dan bidang populasi untuk kode AUS.
Sesi kedua akan mengubah bidang wilayah untuk kode AUS, dan bidang populasi untuk kode NLD.
Data tabel:
code: NLD
region: Western Europe
population: 15864000
code: AUS
region: Australia and New Zealand
population: 18886000
Sesi 1:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Europe' WHERE code='NLD';
UPDATE 1
Sesi 2:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Oceania' WHERE code='AUS';
UPDATE 1
world=# UPDATE country SET population=15864001 WHERE code='NLD';
Sesi 2 akan digantung menunggu Sesi 1 selesai.
Sesi 1:
world=# UPDATE country SET population=18886001 WHERE code='AUS';
ERROR: deadlock detected
DETAIL: Process 1181 waits for ShareLock on transaction 579; blocked by process 1148.
Process 1148 waits for ShareLock on transaction 578; blocked by process 1181.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,15) in relation "country"
Di sini kita mengalami kebuntuan. Sistem mendeteksi kebuntuan dan mematikan sesi 1.
Sesi 2:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Oceania' WHERE code='AUS';
UPDATE 1
world=# UPDATE country SET population=15864001 WHERE code='NLD';
UPDATE 1
Dan kita dapat memeriksa bahwa sesi kedua selesai dengan benar setelah kebuntuan terdeteksi dan Sesi 1 dihentikan (dengan demikian, kunci dilepaskan).
Untuk lebih jelasnya kita bisa melihat log di server PostgreSQL kita:
2018-05-16 12:56:38.520 -03 [1181] ERROR: deadlock detected
2018-05-16 12:56:38.520 -03 [1181] DETAIL: Process 1181 waits for ShareLock on transaction 579; blocked by process 1148.
Process 1148 waits for ShareLock on transaction 578; blocked by process 1181.
Process 1181: UPDATE country SET population=18886001 WHERE code='AUS';
Process 1148: UPDATE country SET population=15864001 WHERE code='NLD';
2018-05-16 12:56:38.520 -03 [1181] HINT: See server log for query details.
2018-05-16 12:56:38.520 -03 [1181] CONTEXT: while updating tuple (0,15) in relation "country"
2018-05-16 12:56:38.520 -03 [1181] STATEMENT: UPDATE country SET population=18886001 WHERE code='AUS';
2018-05-16 12:59:50.568 -03 [1181] ERROR: current transaction is aborted, commands ignored until end of transaction block
Di sini kita akan dapat melihat perintah sebenarnya yang terdeteksi pada kebuntuan.
Unduh Whitepaper Hari Ini Pengelolaan &Otomatisasi PostgreSQL dengan ClusterControlPelajari tentang apa yang perlu Anda ketahui untuk menerapkan, memantau, mengelola, dan menskalakan PostgreSQLUnduh WhitepaperMySQL
Untuk mensimulasikan kebuntuan di MySQL kita dapat melakukan hal berikut.
Seperti halnya PostgreSQL, misalkan kita memiliki database pengujian dengan informasi tentang aktor dan film antara lain.
mysql> SELECT first_name,last_name FROM actor WHERE actor_id IN (1,7);
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| PENELOPE | GUINESS |
| GRACE | MOSTEL |
+------------+-----------+
2 rows in set (0.00 sec)
Kami memiliki dua proses yang ingin membuat perubahan pada database.
Proses pertama akan mengubah field first_name untuk actor_id 1, dan field last_name untuk actor_id 7.
Proses kedua akan mengubah field first_name untuk actor_id 7, dan field last_name untuk actor_id 1.
Data tabel:
actor_id: 1
first_name: PENELOPE
last_name: GUINESS
actor_id: 7
first_name: GRACE
last_name: MOSTEL
Sesi 1:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='GUINESS' WHERE actor_id='1';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Sesi 2:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='MOSTEL' WHERE actor_id='7';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1';
Sesi 2 akan digantung menunggu Sesi 1 selesai.
Sesi 1:
mysql> UPDATE actor SET last_name='GRACE' WHERE actor_id='7';
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Di sini kita mengalami kebuntuan. Sistem mendeteksi kebuntuan dan mematikan sesi 1.
Sesi 2:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='MOSTEL' WHERE actor_id='7';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1';
Query OK, 1 row affected (8.52 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Seperti yang bisa kita lihat di kesalahan, seperti yang kita lihat untuk PostgreSQL, ada kebuntuan di antara kedua proses.
Untuk lebih jelasnya kita bisa menggunakan perintah SHOW ENGINE INNODB STATUS\G:
mysql> SHOW ENGINE INNODB STATUS\G
------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-05-16 18:55:46 0x7f4c34128700
*** (1) TRANSACTION:
TRANSACTION 1456, ACTIVE 33 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 54, OS thread handle 139965388506880, query id 15876 localhost root updating
UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1456 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 0000000005af; asc ;;
2: len 7; hex 2d000001690110; asc - i ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afca8b3; asc Z ;;
*** (2) TRANSACTION:
TRANSACTION 1455, ACTIVE 47 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 53, OS thread handle 139965267871488, query id 16013 localhost root updating
UPDATE actor SET last_name='GRACE' WHERE actor_id='7'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1455 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 0000000005af; asc ;;
2: len 7; hex 2d000001690110; asc - i ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afca8b3; asc Z ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1455 lock_mode X locks rec but not gap waiting
Record lock, heap no 202 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0007; asc ;;
1: len 6; hex 0000000005b0; asc ;;
2: len 7; hex 2e0000016a0110; asc . j ;;
3: len 6; hex 4d4f5354454c; asc MOSTEL;;
4: len 6; hex 4d4f5354454c; asc MOSTEL;;
5: len 4; hex 5afca8c1; asc Z ;;
*** WE ROLL BACK TRANSACTION (2)
Di bawah judul "DEADLOCK TERDETEKSI TERBARU", kami dapat melihat detail kebuntuan kami.
Untuk melihat detail deadlock di mysql error log, kita harus mengaktifkan opsi innodb_print_all_deadlocks di database kita.
mysql> set global innodb_print_all_deadlocks=1;
Query OK, 0 rows affected (0.00 sec)
Kesalahan Log MySQL:
2018-05-17T18:36:58.341835Z 12 [Note] InnoDB: Transactions deadlock detected, dumping detailed information.
2018-05-17T18:36:58.341869Z 12 [Note] InnoDB:
*** (1) TRANSACTION:
TRANSACTION 1812, ACTIVE 42 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 11, OS thread handle 140515492943616, query id 8467 localhost root updating
UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1'
2018-05-17T18:36:58.341945Z 12 [Note] InnoDB: *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1812 lock_mode X locks rec but not gap waiting
Record lock, heap no 204 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 000000000713; asc ;;
2: len 7; hex 330000016b0110; asc 3 k ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afdcb89; asc Z ;;
2018-05-17T18:36:58.342347Z 12 [Note] InnoDB: *** (2) TRANSACTION:
TRANSACTION 1811, ACTIVE 65 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 12, OS thread handle 140515492677376, query id 9075 localhost root updating
UPDATE actor SET last_name='GRACE' WHERE actor_id='7'
2018-05-17T18:36:58.342409Z 12 [Note] InnoDB: *** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1811 lock_mode X locks rec but not gap
Record lock, heap no 204 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 000000000713; asc ;;
2: len 7; hex 330000016b0110; asc 3 k ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afdcb89; asc Z ;;
2018-05-17T18:36:58.342793Z 12 [Note] InnoDB: *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1811 lock_mode X locks rec but not gap waiting
Record lock, heap no 205 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0007; asc ;;
1: len 6; hex 000000000714; asc ;;
2: len 7; hex 340000016c0110; asc 4 l ;;
3: len 6; hex 4d4f5354454c; asc MOSTEL;;
4: len 6; hex 4d4f5354454c; asc MOSTEL;;
5: len 4; hex 5afdcba0; asc Z ;;
2018-05-17T18:36:58.343105Z 12 [Note] InnoDB: *** WE ROLL BACK TRANSACTION (2)
Mempertimbangkan apa yang telah kita pelajari di atas tentang mengapa kebuntuan terjadi, Anda dapat melihat bahwa tidak banyak yang dapat kita lakukan di sisi basis data untuk menghindarinya. Bagaimanapun, sebagai DBA adalah tugas kita untuk benar-benar menangkap mereka, menganalisisnya, dan memberikan umpan balik kepada para pengembang.
Kenyataannya adalah bahwa kesalahan ini khusus untuk setiap aplikasi, jadi Anda perlu memeriksanya satu per satu dan tidak ada panduan untuk memberi tahu Anda cara memecahkan masalah ini. Dengan mengingat hal ini, ada beberapa hal yang dapat Anda cari.
Kiat untuk menyelidiki dan menghindari kebuntuan
Cari transaksi jangka panjang. Karena penguncian biasanya ditahan hingga akhir transaksi, semakin lama transaksi, semakin lama penguncian atas sumber daya. Jika memungkinkan, coba bagi transaksi yang berjalan lama menjadi transaksi yang lebih kecil/cepat.
Terkadang tidak mungkin untuk benar-benar membagi transaksi, sehingga pekerjaan harus fokus pada upaya untuk mengeksekusi operasi tersebut dalam urutan yang konsisten setiap kali, sehingga transaksi membentuk antrian yang terdefinisi dengan baik dan tidak menemui jalan buntu.
Salah satu solusi yang juga dapat Anda usulkan adalah menambahkan logika coba lagi ke dalam aplikasi (tentu saja, coba selesaikan masalah mendasar terlebih dahulu) sedemikian rupa sehingga, jika terjadi kebuntuan, aplikasi akan menjalankan perintah yang sama lagi.
Periksa level isolasi yang digunakan, terkadang Anda mencoba dengan mengubahnya. Cari perintah seperti SELECT FOR UPDATE, dan SELECT FOR SHARE, karena mereka menghasilkan kunci eksplisit, dan evaluasi apakah mereka benar-benar diperlukan atau Anda dapat bekerja dengan snapshot data yang lebih lama. Satu hal yang dapat Anda coba jika Anda tidak dapat menghapus perintah ini adalah menggunakan tingkat isolasi yang lebih rendah seperti READ COMMITTED.
Tentu saja, selalu tambahkan indeks yang dipilih dengan baik ke tabel Anda. Maka kueri Anda perlu memindai lebih sedikit catatan indeks dan akibatnya menyetel lebih sedikit kunci.
Pada tingkat yang lebih tinggi, sebagai DBA Anda dapat mengambil beberapa tindakan pencegahan untuk meminimalkan penguncian secara umum. Untuk menyebutkan satu contoh, dalam hal ini untuk PostgreSQL, Anda dapat menghindari menambahkan nilai default pada perintah yang sama dengan yang akan Anda tambahkan kolom. Mengubah tabel akan mendapatkan kunci yang sangat agresif, dan menetapkan nilai default untuk itu akan benar-benar memperbarui baris yang ada yang memiliki nilai nol, membuat operasi ini memakan waktu sangat lama. Jadi, jika Anda membagi operasi ini menjadi beberapa perintah, menambahkan kolom, menambahkan default, memperbarui nilai nol, Anda akan meminimalkan dampak penguncian.
Tentu saja, ada banyak sekali tip seperti ini yang didapat DBA dengan latihan (membuat indeks secara bersamaan, membuat indeks pk secara terpisah sebelum menambahkan pk, dan seterusnya), tetapi yang penting adalah mempelajari dan memahami "cara berpikir" dan selalu meminimalkan dampak kunci dari operasi yang kita lakukan.
Ringkasan
Semoga blog ini memberikan informasi yang bermanfaat tentang kebuntuan database dan cara mengatasinya. Karena tidak ada cara pasti untuk menghindari kebuntuan, mengetahui cara kerjanya dapat membantu Anda menangkapnya sebelum mereka membahayakan instance database Anda. Solusi perangkat lunak seperti ClusterControl dapat membantu Anda memastikan bahwa database Anda selalu dalam kondisi prima. ClusterControl telah membantu ratusan perusahaan - apakah perusahaan Anda akan menjadi yang berikutnya? Unduh uji coba gratis ClusterControl Anda hari ini untuk melihat apakah itu sesuai dengan kebutuhan database Anda.