Menetapkan batas waktu untuk transaksi di MySQL / InnoDB

11

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 UPDATEsebagai 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_timeoutvariabel 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:

  1. Tentunya harus ada cara untuk memaksa ROLLBACKjika transaksi berlangsung selamanya? Haruskah saya menggunakan daemon untuk membunuh transaksi seperti itu, dan jika demikian, seperti apa daemon itu?
  2. Jika koneksi terputus oleh wait_timeoutatau interactive_timeoutpertengahan transaksi, apakah transaksi dibatalkan? Apakah ada cara untuk menguji ini dari konsol?

Klarifikasi : innodb_lock_wait_timeoutmenetapkan 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_timeouttidak 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 UPDATEsebelum SLEEPbaris, kemudian memulai transaksi kedua dari klien lain yang mencoba ke SELECT ... FOR UPDATEbaris 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.

Trevor Burnham
sumber
Anda baru saja memperbarui pertanyaan Anda untuk sepenuhnya bertentangan dengan pertanyaan awal Anda. Anda menyatakan dengan huruf tebal "Jika transaksi pertama tidak pernah dilakukan atau dibatalkan, maka transaksi kedua akan diblokir tanpa batas waktu." Anda menyanggah hal ini dengan pembaruan terakhir Anda: "ini adalah transaksi kedua yang berakhir, bukan yang tertidur." - serius ?!
hobodave
Saya kira ungkapan saya bisa lebih jelas. Dengan "diblokir tanpa batas," saya maksudkan bahwa transaksi kedua akan habis dengan pesan kesalahan yang mengatakan "coba memulai kembali transaksi"; tetapi melakukan hal itu akan membuahkan hasil, karena itu hanya akan menyendiri lagi. Jadi transaksi kedua diblokir — itu tidak akan pernah selesai, karena akan membuat waktu habis.
Trevor Burnham
@ Trevor: Ungkapan Anda sangat jelas. Anda cukup memperbarui pertanyaan Anda untuk menanyakan hal yang sama sekali berbeda, yang merupakan bentuk yang sangat buruk.
hobodave
Pertanyaannya selalu sama: Ini masalah bahwa transaksi kedua diblokir tanpa batas waktu ketika transaksi pertama tidak pernah dilakukan atau dibatalkan. Saya ingin memaksakan ROLLBACKtransaksi pertama jika perlu lebih dari ndetik untuk menyelesaikannya. Apakah ada cara untuk melakukannya?
Trevor Burnham
1
@ TrevorBurnham Saya juga bertanya-tanya mengapa tidak MYSQLmemiliki 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.
Dinoop paloli

Jawaban:

3

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:

Jika transaksi pertama tidak pernah dilakukan atau dibatalkan, maka transaksi kedua akan diblokir tanpa batas waktu

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:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • Menurut definisi, ini bukan tidak pasti . Jika aplikasi Anda menghubungkan kembali dan memblokir berulang kali maka aplikasi Anda "memblokir tanpa batas", bukan transaksi. Blok transaksi kedua sangat jelas selama beberapa innodb_lock_wait_timeoutdetik.

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:

Transaksi saat ini tidak dibatalkan. (Untuk mengembalikan seluruh transaksi, mulai server dengan --innodb_rollback_on_timeoutopsi.


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:

Saya sebenarnya lebih khawatir dalam pertanyaan saya dengan skenario di mana aplikasi klien hang (katakanlah, terperangkap dalam infinite loop) selama transaksi daripada dengan di mana transaksi membutuhkan waktu lama di akhir MySQL.

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 .

hobodave
sumber
Maaf, tetapi sementara benar ketika transaksi sedang menunggu kunci (seperti nama dan dokumentasi innodb_lock_wait_timeoutmenyarankan), itu tidak begitu dalam situasi yang saya ajukan: di mana klien hang atau crash sebelum mendapat perubahan untuk melakukan COMMITatau ROLLBACK.
Trevor Burnham
@ Trevor: Anda benar-benar salah. Ini sepele untuk diuji di pihak Anda. Saya baru saja mengujinya. Silakan ambil kembali downvote Anda.
hobodave
@ hobo, hanya karena hakmu bukan berarti dia harus menyukainya.
Chris S
Chris, bisakah kamu menjelaskan bagaimana dia benar? Bagaimana transaksi kedua terjadi jika tidak ada COMMITatau ROLLBACKpesan dikirim untuk menyelesaikan transaksi pertama? Hobodave, mungkin akan sangat membantu jika Anda mempresentasikan kode tes Anda.
Trevor Burnham
2
@ Trevor: Anda tidak masuk akal. Anda ingin membuat serialisasi transaksi, bukan? Ya, Anda mengatakannya dalam pertanyaan Anda yang lain dan mengulanginya di sini dalam pertanyaan ini. Jika transaksi pertama belum selesai, bagaimana Anda mengharapkan transaksi kedua selesai? Anda telah secara eksplisit mengatakannya untuk menunggu kunci dirilis pada baris itu. Karena itu, harus menunggu. Apa yang tidak Anda mengerti tentang ini?
hobodave
1

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.

Krešimir Nesek
sumber
0

Sebuah solusi sederhana adalah memiliki prosedur tersimpan untuk membunuh permintaan yang membutuhkan waktu lebih lama dari timeoutwaktu yang diperlukan dan menggunakan innodb_rollback_on_timeoutopsi yang menyertainya.

Sony Mathew
sumber
-2

Setelah Googling berkeliling sebentar, sepertinya tidak ada cara langsung untuk menetapkan batas waktu per transaksi. Inilah solusi terbaik yang dapat saya temukan:

Perintah

SHOW PROCESSLIST;

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 Sleepperintah, dengan Timekolom menjadi jumlah detik sejak perintah terakhir mereka selesai. Jadi, Anda bisa masuk secara manual dan KILLsegala sesuatu yang memiliki Timenilai 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 di Timekolomnya, Anda bisa melakukannya

KILL 3;

Dokumentasi 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 SELECTaktif SHOW PROCESSLISTdari dalam klien MySQL. Namun, seandainya kita memiliki daemon PHP yang kita jalankan setiap $MAX_TIMEdetik, itu mungkin terlihat seperti ini:

$result = mysql_query("SHOW FULL PROCESSLIST");
while ($row=mysql_fetch_array($result)) {
  $process_id=$row["Id"];
    if ($row["Time"] > $MAX_TIME) {
      $sql="KILL $process_id";
      mysql_query($sql);
  }
}

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 KILLcara ini. Misalkan Anda menggunakan skrip di atas untuk KILLsetiap koneksi yang diam selama 10 detik. Setelah koneksi diam selama 10 detik, tetapi sebelum KILLdikeluarkan, pengguna yang koneksinya terputus akan mengeluarkan START TRANSACTIONperintah. Mereka kemudian KILLed. Mereka mengirim UPDATEdan kemudian, berpikir dua kali, mengeluarkan a ROLLBACK. Namun, karena KILLmengembalikan transaksi asli mereka, pembaruan segera berjalan dan tidak dapat dibatalkan! Lihat posting ini untuk test case.

Trevor Burnham
sumber
2
Komentar ini ditujukan untuk pembaca masa depan dari jawaban ini. Tolong jangan lakukan ini, bahkan jika itu adalah jawaban yang diterima.
hobodave
OK, daripada downvoting dan meninggalkan komentar sinis, bagaimana menjelaskan mengapa ini akan menjadi ide yang buruk? Orang yang memposting skrip PHP asli mengatakan bahwa itu menjaga server mereka dari menggantung; jika ada cara yang lebih baik untuk mencapai tujuan yang sama, tunjukkan padaku.
Trevor Burnham
6
Komentar itu tidak sinis. Ini adalah peringatan bagi semua pembaca di masa depan untuk tidak berusaha menggunakan "solusi" Anda. Secara otomatis membunuh transaksi yang sudah berjalan lama adalah ide yang buruk secara sepihak. Seharusnya tidak pernah dilakukan . Itulah penjelasannya. Sesi pembunuhan harus menjadi pengecualian, bukan norma. The akar penyebab masalah buat Anda adalah kesalahan pengguna. Anda tidak membuat solusi teknis untuk DBA yang mengunci baris dan kemudian "berjalan pergi". Anda memecat mereka.
hobodave
-2

Seperti yang disarankan hobodave dalam komentar, ada kemungkinan di beberapa klien (walaupun tidak, mysqlutilitas baris perintah) untuk menetapkan batas waktu untuk transaksi. Berikut ini demonstrasi menggunakan ActiveRecord di Ruby:

require 'rubygems'
require 'timeout'
require 'active_record'

Timeout::timeout(5) {
  Foo.transaction do
    Foo.create(:name => 'Bar')
    sleep 10
  end
}

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 ndetik, 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 KILLdikeluarkan, 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.

Trevor Burnham
sumber
Anda telah mengabaikan bahwa ActiveRecord menggunakan I / O yang sinkron (memblokir). Semua skrip yang dilakukan mengganggu perintah tidur Ruby. Jika Foo.createperintah Anda diblokir selama 5 menit, Timeout tidak akan membunuhnya. Ini tidak sesuai dengan contoh yang diberikan dalam pertanyaan Anda. A SELECT ... FOR UPDATEdapat dengan mudah memblokir untuk jangka waktu yang lama, seperti halnya kueri lainnya.
hobodave
Perlu dicatat bahwa hampir semua perpustakaan klien mysql menggunakan pemblokiran I / O, maka saran dalam jawaban saya bahwa Anda perlu menggunakan atau menulis sendiri yang menggunakan I / O non-pemblokiran. Berikut ini adalah demonstrasi sederhana dari Timeout menjadi tidak berguna: gist.github.com/856100
hobodave
Saya sebenarnya lebih khawatir dalam pertanyaan saya dengan skenario di mana aplikasi klien hang (katakanlah, terperangkap dalam infinite loop) selama transaksi daripada dengan di mana transaksi membutuhkan waktu lama di akhir MySQL. Tapi terima kasih, itu poin bagus. Saya telah memperbarui pertanyaan untuk dicatat.)
Trevor Burnham
Saya tidak dapat menduplikasi hasil yang Anda klaim. Saya memperbarui inti untuk memanfaatkan ActiveRecord::Base#find_by_sql: gist.github.com/856100 Sekali lagi, ini karena AR menggunakan pemblokiran I / O.
hobodave
@hobodave Ya, pengeditan itu salah dan telah diperbaiki. Untuk menjadi jelas: timeoutMetode ini akan memutar kembali transaksi, tetapi tidak sampai database MySQL menanggapi permintaan. Jadi timeoutmetode 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.
Trevor Burnham