サーバー:MySQL 5.6
ストアドプロシージャ:
DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN
SELECT SLEEP(t);
INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;
Goコード:
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)
}
}
私たちは奇妙な行動に遭遇しています。
mysqlクライアント(例:sequel pro)でslowInsert( 10 )
を実行すると、挿入前に10秒間の待機が尊重されます。 ストアドプロシージャが間違っている、または無効であると信じる理由はありません。
上記のGoコードを実行すると、コンテキストは3秒後にキャンセルされます-すべて良好です。
...しかし、すぐに挿入が発生します。 (10秒待ちません)。
ここで、ストアドプロシージャを次のように変更すると、次のようになります。
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 ;
Goコードは完全に機能し、クエリをキャンセルします(つまり、挿入は行われません)。
どちらの場合も、mysqlクライアント(sequel pro)は待機期間を尊重するため、ストアドプロシージャは両方とも同等です。
しかし、Goコードでは、2番目のストアドプロシージャは機能しますが、最初のプロシージャは機能しません。これにより、ドライバーに奇妙なバグがあると考えられます。
@ alessio-palumbo
バグではありません。 クエリのキャンセルはまだサポートされていません。
そして、 SLEEP()
は非常に特別なクエリです。 接続が有効かどうかをチェックします。
通常、MySQLはクエリが終了するまで接続をチェックしません。
Readme: https :
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は誤解を招く恐れがあります。 クエリの実行ではなく、「待機中」のクエリ結果をキャンセルするだけです。
MySQLプロトコルは、クエリの実行をキャンセルする安全な方法を提供していません。
一部のCLIはキャンセルをサポートしていますが、一部の環境では安全/安定していません。
ありがとう
あなたがすべきことは、トランザクションを使用することです。 コミットしない場合は、ロールバックされます。
したがって、トランザクションが安全である間にタイムアウトします。
@andizzle @alexclifford @edwardhutchison
タイムアウト後に最初のstoredProcが挿入されるのに、2番目のstoredProcが挿入されないのはなぜですか。 あなたの声明に基づいて、挿入するか挿入しないかのどちらかだと思います。 ドライバーがクエリ実行のキャンセルをサポートしていない場合、なぜ実行がキャンセルされたように見えるのですか?
これはMySQLの動作です。 よくわかりません。 ただし、接続が閉じられている場合、SLEEP()の動作は非常に奇妙です。
@julienschmidtこのアイデアについてどう思いますか: https :
SHOW FULL PROCESSLIST
とKILL
を使用して、適切なクエリにKILL
を送信できる場合があります。
残念ながら、それをドライバーレベルで簡単に実装することはできません。 ドライバーがこれらのコマンドを実行する2番目の接続が必要になります。 これは、共有の「管理接続」またはオンデマンドで開かれる接続のいずれかです。
まず第一に、これはドライバーをはるかに複雑にするでしょう。 現在、セッションと接続のすべての処理は、新しい接続などを開くための機能を提供するだけのドライバーではなく、 database/sql
パッケージによって実行されます。
しかし、もっと問題なのは、適切なサーバーを見つける方法が実際にはないということです(サーバーが1つしかない場合を除く)。 ドライバーは、主にmysql負荷分散プロキシとともにデプロイされます。 その場合、 SHOW FULL PROCESSLIST
が間違ったサーバーで実行され、クエリが見つからない可能性があります。
クエリを本当にキャンセル可能にするために必要なものをdatabase/sql
に変更するために、Go 2の提案を行うことができますか?
ここでの問題はdatabase/sql
やこのドライバーではなく、実行中のクエリをキャンセルする簡単な方法を提供します。
単一サーバーのセットアップの場合、 Postgresと同様の方法でクエリをキャンセルできます。新しい接続を開き、すぐにKILL
コマンドを送信します。
一部のプロキシを使用する場合は、より困難です。 現在、このようなプロキシはドライバーに対して完全に透過的です。 必要なのは、接続が別の既存の接続と同じバックエンドに対して開かれることを保証するためのある種のメカニズムです。
実際には、同様のアプローチ(単一サーバーのセットアップの場合)のオープンPRがすでにあります: https :
MySQLで機能リクエストを送信します。 プロセスIDを含むクエリを要求した場合、それは後方互換性のない変更を作成し、拒否をフラットにしますか?
キャンセルの問題だけではありません。 他にもいくつか問題があります。
地球上には「リモートでの信頼できるキャンセル」プロトコルはありません。
一部のCPUを節約するためにのみ使用する必要があります。
とにかく、この問題のポイントは何ですか?
キャンセルを実装しても、奇妙な動作は避けられません。
MySQLサーバーで「コンテキストによってキャンセルされた」クエリが実行またはキャンセルされたことを知ることはできません。 タイミングの問題です。
あなただけができるのはトランザクションを使うことです。 すべてのクエリがロールバックされます。
「キャンセルクエリの実装」の問題が本当に必要な場合は、新しい問題を提出してこれを閉じます。
これが私の解決策です: https ://medium.com/@rocketlaunchr.cloud/canceling -mysql-in-go-827ed8f83b30
最も参考になるコメント
残念ながら、それをドライバーレベルで簡単に実装することはできません。 ドライバーがこれらのコマンドを実行する2番目の接続が必要になります。 これは、共有の「管理接続」またはオンデマンドで開かれる接続のいずれかです。
まず第一に、これはドライバーをはるかに複雑にするでしょう。 現在、セッションと接続のすべての処理は、新しい接続などを開くための機能を提供するだけのドライバーではなく、
database/sql
パッケージによって実行されます。しかし、もっと問題なのは、適切なサーバーを見つける方法が実際にはないということです(サーバーが1つしかない場合を除く)。 ドライバーは、主にmysql負荷分散プロキシとともにデプロイされます。 その場合、
SHOW FULL PROCESSLIST
が間違ったサーバーで実行され、クエリが見つからない可能性があります。