Mysql: Perilaku aneh dengan pembatalan kueri menggunakan konteks

Dibuat pada 2 Okt 2018  ·  19Komentar  ·  Sumber: go-sql-driver/mysql

Server: MySQL 5.6

Proc Tersimpan:

DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN       
       SELECT SLEEP(t);
       INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;

Buka Kode:

package main

import (
    "context"
    "database/sql"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "<url>")
    if err != nil {
        panic(err)
    }
    db.SetConnMaxLifetime(9 * time.Second)
    db.SetMaxIdleConns(12)
    db.SetMaxOpenConns(12)

    ctx := context.Background()
    ctx, cancel := context.WithTimeout(ctx, time.Duration(3)*time.Second)
    defer cancel()

    _, err = db.ExecContext(ctx, "call slowInsert( 10 )") // context will cancel before insert occurs
    if err != nil {
        panic(err)
    }
}

Komentar yang paling membantu

Kami mungkin dapat menggunakan SHOW FULL PROCESSLIST dan KILL untuk mengirim KILL ke kueri yang tepat.

Sayangnya itu tidak bisa diimplementasikan dengan mudah di level driver. Ini akan membutuhkan koneksi ke-2 di mana driver menjalankan perintah ini. Itu bisa berupa "koneksi manajemen" bersama atau koneksi yang dibuka sesuai permintaan.

Pertama-tama, ini akan membuat pengemudi jauh lebih kompleks. Saat ini semua penanganan sesi dan koneksi dilakukan oleh paket database/sql , bukan driver, yang hanya menyediakan fungsi untuk membuka koneksi baru dll.

Tetapi jauh lebih bermasalah adalah, bahwa kita sebenarnya tidak memiliki cara untuk menemukan server yang tepat (kecuali hanya ada satu). Driver sebagian besar digunakan dengan proxy load-balancing mysql. Dalam hal ini, kemungkinan SHOW FULL PROCESSLIST dijalankan di server yang salah dan kami tidak dapat menemukan kuerinya.

Semua 19 komentar

Kami menghadapi perilaku aneh.
Ketika kita menjalankan slowInsert( 10 ) pada klien mysql (mis. sekuel pro), 10 detik menunggu dihormati sebelum penyisipan. Kami tidak memiliki alasan untuk percaya bahwa proc yang disimpan salah atau tidak valid.

Saat kami menjalankan kode Go di atas, konteksnya dibatalkan setelah 3 detik -- semuanya baik-baik saja.
... Tapi segera penyisipan terjadi. (Tidak menunggu 10 detik).

Sekarang, jika kita mengubah proc yang disimpan menjadi:

DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN       
       SELECT SLEEP(t/2); // <---- The change
       SELECT SLEEP(t/2); // <---- The change
       INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;

Kode Go bekerja dengan sempurna dan membatalkan kueri (yaitu tidak ada penyisipan yang terjadi).
Dalam kedua kasus, klien mysql kami (sekuel pro) menghormati masa tunggu sehingga kedua procs yang disimpan setara.

Tetapi dalam kode Go kami, proc tersimpan kedua berfungsi tetapi yang pertama tidak - mengarah pada keyakinan kami bahwa ada beberapa bug aneh pada driver.

@ alessio-palumbo

Ini bukan bug. Kami belum mendukung pembatalan kueri.
Dan SLEEP() adalah kueri yang sangat istimewa. Ini memeriksa koneksi hidup atau tidak.
Dalam kasus umum, MySQL tidak memeriksa koneksi sampai query selesai.

Readme: https://github.com/go-sql-driver/mysql#contextcontext -support

Go 1.8 added database/sql support for context.Context. This driver supports query timeouts and cancellation via contexts. See context support in the database/sql package for more details.

OK, README menyesatkan. Itu hanya membatalkan hasil kueri "menunggu", bukan eksekusi kueri.

Protokol MySQL tidak menyediakan cara yang aman untuk membatalkan eksekusi kueri.
Beberapa CLI mendukung pembatalan, tetapi tidak aman/stabil di beberapa lingkungan.

Terima kasih

Yang harus Anda lakukan adalah menggunakan transaksi. Jika Anda tidak melakukan, itu dibatalkan.
Jadi timeout selama transaksi aman.

@andizzle @alexclifford @edwardhutchison

Mengapa storedProc pertama dimasukkan setelah batas waktu tetapi storedProc kedua tidak dimasukkan. Saya berasumsi bahwa berdasarkan pernyataan Anda, itu dimasukkan atau tidak. Jika driver tidak mendukung pembatalan eksekusi kueri, mengapa sepertinya membatalkan eksekusi?

Ini adalah perilaku MySQL. Saya tidak tahu banyak. Tetapi SLEEP() memiliki perilaku yang sangat aneh ketika koneksi ditutup.

@julienschmidt apa pendapat Anda tentang ide ini: https://github.com/src-d/gitbase-web/issues/241#issuecomment -427740194

Kami mungkin dapat menggunakan SHOW FULL PROCESSLIST dan KILL untuk mengirim KILL ke kueri yang tepat.

Sayangnya itu tidak bisa diimplementasikan dengan mudah di level driver. Ini akan membutuhkan koneksi ke-2 di mana driver menjalankan perintah ini. Itu bisa berupa "koneksi manajemen" bersama atau koneksi yang dibuka sesuai permintaan.

Pertama-tama, ini akan membuat pengemudi jauh lebih kompleks. Saat ini semua penanganan sesi dan koneksi dilakukan oleh paket database/sql , bukan driver, yang hanya menyediakan fungsi untuk membuka koneksi baru dll.

Tetapi jauh lebih bermasalah adalah, bahwa kita sebenarnya tidak memiliki cara untuk menemukan server yang tepat (kecuali hanya ada satu). Driver sebagian besar digunakan dengan proxy load-balancing mysql. Dalam hal ini, kemungkinan SHOW FULL PROCESSLIST dijalankan di server yang salah dan kami tidak dapat menemukan kuerinya.

Apakah Anda dapat membuat proposal Go 2 untuk mengubah apa pun yang diperlukan untuk database/sql agar kueri benar-benar dapat dibatalkan?

Sejujurnya saya berpikir bahwa bukan database/sql atau driver ini yang menjadi masalah di sini, tetapi MySQL sederhana itu sendiri, yang tidak menyediakan

Awal yang baik adalah jika balasan atas kueri yang dikirim ke server akan berisi id proses.

Dalam kasus penyiapan server tunggal, kita kemudian dapat membatalkan kueri dengan cara yang mirip dengan Postgres : Buka koneksi baru dan segera kirim perintah KILL .

Kasus dengan beberapa proxy lebih sulit. Saat ini proxy tersebut benar-benar transparan bagi pengemudi. Apa yang diperlukan adalah semacam mekanisme untuk menjamin bahwa koneksi dibuka ke backend yang sama dengan koneksi lain yang ada.

Kami sebenarnya sudah memiliki PR terbuka untuk pendekatan serupa (untuk kasus penyiapan server tunggal): https://github.com/go-sql-driver/mysql/pull/791

Saya akan mengirimkan permintaan fitur dengan MySQL. Jika saya meminta kueri untuk memuat id proses, apakah itu akan membuat perubahan yang tidak kompatibel ke belakang yang akan mereka tolak?

Ini bukan satu-satunya masalah tentang pembatalan. Ada beberapa masalah lain.
Tidak ada protokol "pembatalan jarak jauh yang andal" di bumi.
Ini harus digunakan hanya untuk menyimpan beberapa CPU.

Lagi pula, apa gunanya masalah ini?
Perilaku aneh tidak dihindari dengan menerapkan pembatalan.
Anda tidak dapat mengetahui permintaan "dibatalkan oleh konteks" dijalankan atau dibatalkan di server MySQL. Ini masalah waktu.
Yang bisa Anda lakukan hanyalah menggunakan transaksi; semua permintaan akan dibatalkan.

Jika kami benar-benar membutuhkan masalah untuk "Terapkan permintaan pembatalan", ajukan masalah baru dan tutup ini.

Inilah solusi saya: https://medium.com/@rocketlaunhr.cloud/canceling -mysql-in-go-827ed8f83b30

Apakah halaman ini membantu?
0 / 5 - 0 peringkat