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 ;

去代码:

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 PROCESSLISTKILLKILL发送到正确的查询。

不幸的是,这不能轻易地在驱动程序级别上实现。 它需要驱动程序执行这些命令的第二个连接。 这可能是共享的“管理连接”或按需打开的连接。

首先,这会使驱动程序变得更加复杂。 现在所有会话和连接的处理都是由database/sql包完成的,而不是驱动程序,它只提供打开新连接等的功能。

但更大的问题是,我们实际上无法找到合适的服务器(除非只有一个)。 该驱动程序主要与 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 代码中,第二个存储过程可以工作,但第一个不能 - 导致我们认为驱动程序中存在一些奇怪的错误。

@alessio-palumbo

这不是一个错误。 我们还不支持取消查询。
SLEEP()是非常特殊的查询。 它检查连接是否存在。
一般情况下,MySQL 在查询完成之前不会检查连接。

自述文件: 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.

好的,自述文件具有误导性。 它只是取消“等待”查询结果,而不是查询执行。

MySQL 协议不提供取消查询执行的安全方法。
一些 CLI 支持取消,但在某些环境下它不安全/稳定。

谢谢

你应该做的是使用事务。 如果你不提交,它就会被回滚。
所以在交易安全时超时。

@andizzle @alexclifford @edwardhutchison

为什么第一个storedProc 在超时后插入,但第二个storedProc 没有插入。 我假设根据您的陈述,它要么插入,要么不插入。 如果驱动程序不支持取消查询执行,为什么它似乎取消执行?

这是 MySQL 的行为。 我知道的不多。 但是 SLEEP() 在连接关闭时有非常奇怪的行为。

@julienschmidt你怎么看这个想法: https :

我们也许可以使用SHOW FULL PROCESSLISTKILLKILL发送到正确的查询。

不幸的是,这不能轻易地在驱动程序级别上实现。 它需要驱动程序执行这些命令的第二个连接。 这可能是共享的“管理连接”或按需打开的连接。

首先,这会使驱动程序变得更加复杂。 现在所有会话和连接的处理都是由database/sql包完成的,而不是驱动程序,它只提供打开新连接等的功能。

但更大的问题是,我们实际上无法找到合适的服务器(除非只有一个)。 该驱动程序主要与 mysql 负载平衡代理一起部署。 在这种情况下,很可能SHOW FULL PROCESSLIST在错误的服务器上执行,我们找不到查询。

您是否能够提出 Go 2 提案来修改database/sql以使查询真正可取消所需的内容?

老实说,我认为不是database/sql或这个驱动程序是这里的问题,而是简单的 MySQL 本身,它没有提供

如果对发送到服务器的查询

在单个服务器设置的情况下,我们可以以类似于 Postgres 的方式取消查询:打开一个新连接并立即发送KILL命令。

一些代理的情况更困难。 现在这样的代理对司机来说是完全透明的。 需要某种机制来保证一个连接打开到与另一个现有连接相同的后端。

实际上,我们已经有一个类似方法的公开 PR(对于单服务器设置案例): https :

我将使用 MySQL 提交功能请求。 如果我要求查询包含进程 ID,这是否会创建一个向后不兼容的更改,他们会完全拒绝?

这不仅仅是取消的问题。 还有一些其他问题。
地球上没有任何“远程可靠取消”协议。
它应该仅用于节省一些 CPU。

无论如何,这个问题的意义何在?
实施取消并不能避免奇怪的行为。
您无法知道查询“由上下文取消”在 MySQL 服务器中执行或取消。 是时间问题。
只有你能做的就是使用事务; 所有查询都将回滚。

如果我们真的需要“实施取消查询”的问题,请提交一个新问题并关闭它。

这是我的解决方案: https: //medium.com/@rocketlaunchr.cloud/canceling -mysql-in-go-827ed8f83b30

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

pedromorgan picture pedromorgan  ·  6评论

mayurshivakumar picture mayurshivakumar  ·  5评论

albrow picture albrow  ·  7评论

AlekSi picture AlekSi  ·  4评论

Dieterbe picture Dieterbe  ·  6评论