Mysql: コンテキストを使用したクエリキャンセルを伴う奇妙な動作

作成日 2018年10月02日  ·  19コメント  ·  ソース: go-sql-driver/mysql

サーバー: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)
    }
}

最も参考になるコメント

SHOW FULL PROCESSLISTKILLを使用して、適切なクエリにKILLを送信できる場合があります。

残念ながら、それをドライバーレベルで簡単に実装することはできません。 ドライバーがこれらのコマンドを実行する2番目の接続が必要になります。 これは、共有の「管理接続」またはオンデマンドで開かれる接続のいずれかです。

まず第一に、これはドライバーをはるかに複雑にするでしょう。 現在、セッションと接続のすべての処理は、新しい接続などを開くための機能を提供するだけのドライバーではなく、 database/sqlパッケージによって実行されます。

しかし、もっと問題なのは、適切なサーバーを見つける方法が実際にはないということです(サーバーが1つしかない場合を除く)。 ドライバーは、主にmysql負荷分散プロキシとともにデプロイされます。 その場合、 SHOW FULL PROCESSLISTが間違ったサーバーで実行され、クエリが見つからない可能性があります。

全てのコメント19件

私たちは奇妙な行動に遭遇しています。
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 PROCESSLISTKILLを使用して、適切なクエリに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

このページは役に立ちましたか?
0 / 5 - 0 評価