服务器: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)
}
}
我们遇到了奇怪的行为。
当我们在 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 PROCESSLIST
和KILL
将KILL
发送到正确的查询。
不幸的是,这不能轻易地在驱动程序级别上实现。 它需要驱动程序执行这些命令的第二个连接。 这可能是共享的“管理连接”或按需打开的连接。
首先,这会使驱动程序变得更加复杂。 现在所有会话和连接的处理都是由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
最有用的评论
不幸的是,这不能轻易地在驱动程序级别上实现。 它需要驱动程序执行这些命令的第二个连接。 这可能是共享的“管理连接”或按需打开的连接。
首先,这会使驱动程序变得更加复杂。 现在所有会话和连接的处理都是由
database/sql
包完成的,而不是驱动程序,它只提供打开新连接等的功能。但更大的问题是,我们实际上无法找到合适的服务器(除非只有一个)。 该驱动程序主要与 mysql 负载平衡代理一起部署。 在这种情况下,很可能
SHOW FULL PROCESSLIST
在错误的服务器上执行,我们找不到查询。