Ini muncul dari pertanyaan terkait ini , di mana saya ingin tahu bagaimana memaksa dua transaksi terjadi secara berurutan dalam kasus sepele (di mana keduanya beroperasi hanya pada satu baris). Saya mendapat jawaban — gunakan SELECT ... FOR UPDATE
sebagai baris pertama dari kedua transaksi — tetapi ini menimbulkan masalah: Jika transaksi pertama tidak pernah dilakukan atau dibatalkan, maka transaksi kedua akan diblokir tanpa batas waktu. The innodb_lock_wait_timeout
variabel menetapkan jumlah detik setelah klien mencoba untuk membuat transaksi kedua akan diberitahu "Maaf, coba lagi" ... tetapi sejauh yang saya tahu, mereka akan mencoba lagi sampai server reboot berikutnya. Begitu:
- Tentunya harus ada cara untuk memaksa
ROLLBACK
jika transaksi berlangsung selamanya? Haruskah saya menggunakan daemon untuk membunuh transaksi seperti itu, dan jika demikian, seperti apa daemon itu? - Jika koneksi terputus oleh
wait_timeout
atauinteractive_timeout
pertengahan transaksi, apakah transaksi dibatalkan? Apakah ada cara untuk menguji ini dari konsol?
Klarifikasi : innodb_lock_wait_timeout
menetapkan jumlah detik bahwa suatu transaksi akan menunggu kunci dirilis sebelum menyerah; apa yang saya inginkan adalah cara memaksa kunci untuk dilepaskan.
Pembaruan 1 : Berikut adalah contoh sederhana yang menunjukkan mengapa innodb_lock_wait_timeout
tidak cukup untuk memastikan bahwa transaksi kedua tidak diblokir oleh yang pertama:
START TRANSACTION;
SELECT SLEEP(55);
COMMIT;
Dengan pengaturan default innodb_lock_wait_timeout = 50
, transaksi ini selesai tanpa kesalahan setelah 55 detik. Dan jika Anda menambahkan UPDATE
sebelum SLEEP
baris, kemudian memulai transaksi kedua dari klien lain yang mencoba ke SELECT ... FOR UPDATE
baris yang sama, itu adalah transaksi kedua yang habis, bukan yang tertidur.
Apa yang saya cari adalah cara untuk memaksa tidur nyenyak dari transaksi ini.
Pembaruan 2 : Menanggapi kekhawatiran hobodave tentang seberapa realistis contoh di atas, berikut ini skenario alternatif: DBA terhubung ke server langsung dan berjalan
START TRANSACTION
SELECT ... FOR UPDATE
di mana baris kedua mengunci baris yang sering ditulis aplikasi. Kemudian DBA terganggu dan berjalan pergi, lupa untuk mengakhiri transaksi. Aplikasi terhenti hingga baris tidak terkunci. Saya ingin meminimalkan waktu aplikasi macet karena kesalahan ini.
ROLLBACK
transaksi pertama jika perlu lebih darin
detik untuk menyelesaikannya. Apakah ada cara untuk melakukannya?MYSQL
memiliki konfigurasi untuk mencegah skenario ini. Karena tidak dapat diterima server hang karena klien tidak bertanggung jawab. Saya tidak menemukan kesulitan untuk memahami pertanyaan Anda juga sangat relevan.Jawaban:
Lebih dari setengah utas ini tampaknya tentang cara mengajukan pertanyaan di ServerFault. Saya pikir pertanyaannya masuk akal dan cukup sederhana: Bagaimana Anda secara otomatis mengembalikan transaksi yang macet?
Salah satu solusinya, jika Anda ingin mematikan seluruh koneksi, adalah mengatur wait_timeout / interactive_timeout. Lihat /programming/9936699/mysql-rollback-on-transaction-with-lost-disconnected-connection .
sumber
Karena pertanyaan Anda diajukan di ServerFault, masuk akal untuk berasumsi bahwa Anda mencari solusi MySQL untuk masalah MySQL, khususnya dalam bidang pengetahuan yang akan dimiliki oleh seorang administrator sistem dan / atau DBA. Dengan demikian, pertanyaan berikut bagian membahas pertanyaan Anda:
Tidak, tidak akan. Saya pikir Anda tidak mengerti
innodb_lock_wait_timeout
. Itu tidak persis apa yang Anda butuhkan.Itu akan kembali dengan kesalahan seperti yang dinyatakan dalam manual:
innodb_lock_wait_timeout
detik.Secara default, transaksi tidak akan dibatalkan. Merupakan tanggung jawab kode aplikasi Anda untuk memutuskan bagaimana menangani kesalahan ini, apakah itu mencoba lagi, atau memutar balik.
Jika Anda ingin rollback otomatis, itu juga dijelaskan dalam manual:
RE: Banyak pembaruan dan komentar Anda
Pertama, Anda telah menyatakan dalam komentar Anda bahwa Anda "bermaksud" untuk mengatakan bahwa Anda ingin cara untuk menghentikan transaksi pertama yang memblokir tanpa batas. Ini tidak terlihat dari pertanyaan awal Anda dan konflik dengan "Jika transaksi pertama tidak pernah dilakukan atau dibatalkan, maka transaksi kedua akan diblokir tanpa batas".
Meskipun demikian, saya dapat menjawab pertanyaan itu juga. Protokol MySQL tidak memiliki "batas waktu permintaan". Ini berarti Anda tidak dapat menghentikan transaksi yang diblokir pertama kali. Anda harus menunggu sampai selesai, atau membunuh sesi. Ketika sesi terbunuh, server akan secara otomatis mengembalikan transaksi.
Satu-satunya alternatif lain adalah menggunakan atau menulis pustaka mysql yang menggunakan I / O non-pemblokiran yang akan memungkinkan aplikasi Anda untuk mematikan utas / fork membuat kueri setelah N detik. Implementasi dan penggunaan perpustakaan seperti itu berada di luar cakupan ServerFault. Ini adalah pertanyaan yang tepat untuk StackOverflow .
Kedua, Anda menyatakan hal berikut dalam komentar Anda:
Ini sama sekali tidak jelas dalam pertanyaan awal Anda, dan masih tidak. Ini hanya bisa dilihat setelah Anda membagikan berita gembira yang agak penting ini di komentar.
Jika ini sebenarnya masalah yang Anda coba selesaikan, maka saya khawatir Anda telah menanyakannya di forum yang salah. Anda telah menjelaskan masalah pemrograman tingkat aplikasi yang memerlukan solusi pemrograman, yang tidak dapat disediakan oleh MySQL, dan berada di luar cakupan komunitas ini. Jawaban terakhir Anda menjawab pertanyaan "Bagaimana cara mencegah program Ruby dari pengulangan yang tak terbatas?". Pertanyaan itu di luar topik untuk komunitas ini dan harus ditanyakan di StackOverflow .
sumber
innodb_lock_wait_timeout
menyarankan), itu tidak begitu dalam situasi yang saya ajukan: di mana klien hang atau crash sebelum mendapat perubahan untuk melakukanCOMMIT
atauROLLBACK
.COMMIT
atauROLLBACK
pesan dikirim untuk menyelesaikan transaksi pertama? Hobodave, mungkin akan sangat membantu jika Anda mempresentasikan kode tes Anda.Dalam situasi serupa, tim saya memutuskan untuk menggunakan pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Itu dapat melaporkan atau membunuh permintaan yang (antara lain) berjalan terlalu lama. Maka dimungkinkan untuk menjalankannya dalam cron setiap X menit.
Masalahnya adalah bahwa kueri yang memiliki waktu eksekusi yang lama dan tak terduga (mis. Tidak terbatas) mengunci prosedur pencadangan yang mencoba menyiram tabel dengan kunci baca, yang pada gilirannya mengunci semua pertanyaan lain pada "menunggu tabel untuk disiram" yang kemudian tidak pernah kehabisan waktu karena mereka tidak menunggu kunci, yang mengakibatkan tanpa kesalahan dalam aplikasi, yang akhirnya menghasilkan tanpa peringatan kepada administrator dan aplikasi berhenti.
Perbaikannya jelas untuk memperbaiki permintaan awal, tetapi untuk menghindari situasi yang tidak sengaja terjadi di masa depan, akan lebih baik jika mungkin untuk secara otomatis membunuh (atau melaporkan) transaksi / permintaan yang bertahan lebih lama dari waktu tertentu, penulis pertanyaan mana yang tampaknya bertanya. Ini bisa dilakukan dengan pt-kill.
sumber
Sebuah solusi sederhana adalah memiliki prosedur tersimpan untuk membunuh permintaan yang membutuhkan waktu lebih lama dari
timeout
waktu yang diperlukan dan menggunakaninnodb_rollback_on_timeout
opsi yang menyertainya.sumber
Setelah Googling berkeliling sebentar, sepertinya tidak ada cara langsung untuk menetapkan batas waktu per transaksi. Inilah solusi terbaik yang dapat saya temukan:
Perintah
memberi Anda tabel dengan semua perintah yang saat ini sedang dijalankan dan waktu (dalam detik) sejak mereka mulai. Ketika klien telah berhenti memberikan perintah, maka mereka digambarkan sebagai memberikan
Sleep
perintah, denganTime
kolom menjadi jumlah detik sejak perintah terakhir mereka selesai. Jadi, Anda bisa masuk secara manual danKILL
segala sesuatu yang memilikiTime
nilai lebih, katakanlah, 5 detik jika Anda yakin tidak ada permintaan yang akan membutuhkan lebih dari 5 detik pada basis data Anda. Misalnya, jika proses dengan id 3 memiliki nilai 12 diTime
kolomnya, Anda bisa melakukannyaDokumentasi untuk sintaks KILL menunjukkan bahwa transaksi yang dilakukan oleh utas dengan id itu akan dibatalkan (meskipun saya belum menguji ini, jadi berhati-hatilah).
Tetapi bagaimana cara mengotomatisasi ini dan membunuh semua skrip overtime setiap 5 detik? Komentar pertama pada halaman yang sama memberi kita petunjuk, tetapi kodenya ada di PHP; tampaknya kita tidak dapat melakukan
SELECT
aktifSHOW PROCESSLIST
dari dalam klien MySQL. Namun, seandainya kita memiliki daemon PHP yang kita jalankan setiap$MAX_TIME
detik, itu mungkin terlihat seperti ini:Perhatikan bahwa ini akan memiliki efek samping dari memaksa semua koneksi klien idle untuk menyambung kembali, bukan hanya mereka yang melakukan kesalahan.
Jika ada yang punya jawaban yang lebih baik, saya ingin mendengarnya.
Sunting: Setidaknya ada satu risiko serius menggunakan
KILL
cara ini. Misalkan Anda menggunakan skrip di atas untukKILL
setiap koneksi yang diam selama 10 detik. Setelah koneksi diam selama 10 detik, tetapi sebelumKILL
dikeluarkan, pengguna yang koneksinya terputus akan mengeluarkanSTART TRANSACTION
perintah. Mereka kemudianKILL
ed. Mereka mengirimUPDATE
dan kemudian, berpikir dua kali, mengeluarkan aROLLBACK
. Namun, karenaKILL
mengembalikan transaksi asli mereka, pembaruan segera berjalan dan tidak dapat dibatalkan! Lihat posting ini untuk test case.sumber
Seperti yang disarankan hobodave dalam komentar, ada kemungkinan di beberapa klien (walaupun tidak,
mysql
utilitas baris perintah) untuk menetapkan batas waktu untuk transaksi. Berikut ini demonstrasi menggunakan ActiveRecord di Ruby:Dalam contoh ini, transaksi habis setelah 5 detik dan secara otomatis dibatalkan . ( Pembaruan dalam menanggapi komentar hobodave: Jika database membutuhkan lebih dari 5 detik untuk merespons, transaksi akan dibatalkan segera setelah itu — tidak lebih cepat.) Jika Anda ingin memastikan bahwa semua transaksi Anda habis setelah beberapa
n
detik, Anda bisa membuat pembungkus di sekitar ActiveRecord. Saya menduga bahwa ini juga berlaku untuk perpustakaan paling populer di Jawa, .NET, Python, dll., Tetapi saya belum mengujinya. (Jika sudah, silakan kirim komentar tentang jawaban ini.)Transaksi dalam ActiveRecord juga memiliki keuntungan menjadi aman jika
KILL
dikeluarkan, tidak seperti transaksi yang dilakukan dari baris perintah. Lihat /dba/1561/mysql-client-believes-theyre-in-a-ttransaction-gets-killed-wreaks-havoc .Tampaknya tidak mungkin untuk memaksakan waktu transaksi maksimum di sisi server, kecuali melalui skrip seperti yang saya posting di jawaban saya yang lain.
sumber
Foo.create
perintah Anda diblokir selama 5 menit, Timeout tidak akan membunuhnya. Ini tidak sesuai dengan contoh yang diberikan dalam pertanyaan Anda. ASELECT ... FOR UPDATE
dapat dengan mudah memblokir untuk jangka waktu yang lama, seperti halnya kueri lainnya.ActiveRecord::Base#find_by_sql
: gist.github.com/856100 Sekali lagi, ini karena AR menggunakan pemblokiran I / O.timeout
Metode ini akan memutar kembali transaksi, tetapi tidak sampai database MySQL menanggapi permintaan. Jaditimeout
metode ini cocok untuk mencegah transaksi panjang di sisi klien atau transaksi panjang yang terdiri dari beberapa permintaan yang relatif singkat, tetapi tidak untuk transaksi panjang di mana satu permintaan adalah pelakunya.