go 数据库/SQL:在具有多个打开连接的DB池上执行语句时,准备命令始终会被再次发送,

yebdmbv4  于 5个月前  发布在  Go
关注(0)|答案(8)|浏览(45)

你在做什么?

在至少有两个打开的数据库连接池上执行一个预准备的SQL查询,总是会在两个连接上发送Prepare命令。
为了重现错误,首先在你的MySQL数据库中启用一般日志记录:

SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'table';

现在,连接到你的mysql数据库:

package main

import (
	"sync"

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

db, err := sql.Open("mysql", "***:***@/***")
if err != nil {
    panic(err.Error())
}
defer db.Close()

db.SetMaxIdleConns(10)
db.SetMaxOpenConns(10)

然后运行一些并发的SQL命令,以确保连接池中有多个打开的连接:

var wg = &sync.WaitGroup{}
for i := 0; i < 2; i++ {
	wg.Add(1)
	go func(i int) {
		var stmt, err = db.Prepare("SELECT * FROM mytable WHERE id = ?")
		if err != nil {
			panic(err.Error())
		}
		defer stmt.Close()
		_, err = stmt.Exec(1)
		if err != nil {
			panic(err.Error())
		}
		wg.Done()
	}(i)
}
wg.Wait()

现在我们至少有两个打开的连接,在数据库上运行一个预准备和执行命令:

var stmt, err = db.Prepare("SELECT * FROM mytable WHERE id = ?")
if err != nil {
	panic(err.Error())
}
defer stmt.Close()
_, err = stmt.Exec(2)
if err != nil {
	panic(err.Error())
}

你期望看到什么?

mysql> SELECT thread_id,command_type,argument FROM mysql.general_log;

+-----------+--------------+------------------------------------------+
| thread_id | command_type | argument                                 |
+-----------+--------------+------------------------------------------+
|        78 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        77 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        78 | Execute      | SELECT * FROM mytable WHERE id = 1       |
|        77 | Execute      | SELECT * FROM mytable WHERE id = 1       |
|        78 | Close stmt   | 
|        77 | Close stmt   |                
|        78 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        78 | Execute      | SELECT * FROM mytable WHERE id = 2       |
|        78 | Close stmt   |                       
|        78 | Quit         |                            
|        77 | Quit         |                        
+-----------+--------------+------------------------------------------+

你看到的是什么?

mysql> SELECT thread_id,command_type,argument FROM mysql.general_log;

+-----------+--------------+------------------------------------------+
| thread_id | command_type | argument                                 |
+-----------+--------------+------------------------------------------+
|        38 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        38 | Execute      | SELECT * FROM mytable WHERE id = 1       |
|        38 | Close stmt   |   
|        37 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        38 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        38 | Execute      | SELECT * FROM mytable WHERE id = 1       |
|        38 | Close stmt   |  
|        37 | Close stmt   | 
|        37 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        38 | Prepare      | SELECT * FROM mytable WHERE id = ?       |
|        38 | Execute      | SELECT * FROM mytable WHERE id = 2       |
|        38 | Close stmt   |        
|        38 | Quit         |
|        37 | Close stmt   |    
|        37 | Quit         |
+-----------+--------------+------------------------------------------+

系统详细信息

mysql  Ver 8.0.16 for Linux on x86_64 (MySQL Community Server - GPL)
go version go1.12.5 darwin/amd64
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/najani/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/najani/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
GOROOT/bin/go version: go version go1.12.5 darwin/amd64
GOROOT/bin/go tool compile -V: compile version go1.12.5
uname -v: Darwin Kernel Version 18.2.0: Fri Oct  5 19:41:49 PDT 2018; root:xnu-4903.221.2~2/RELEASE_X86_64
ProductName:	Mac OS X
ProductVersion:	10.14.1
BuildVersion:	18B75
lldb --version: lldb-1000.11.38.2
  Swift-4.2
aamkag61

aamkag611#

经过调查,我们发现 connStmt 函数从未找到 prepare 命令发送的连接,因为 db.conn 函数调用实现为后进先出(LIFO)模式,这意味着在 prepareexecute 之间没有发生其他事情时,将返回用于 prepare 的最后一个连接。
参考:https://github.com/golang/go/blob/master/src/database/sql/sql.go#L2461-L2478
我们通过实现一个遍历 DB.freeConnStmt.css 以查找匹配项的函数来测试这个假设。这解决了这个问题,我们将提交一个 PR。

qvk1mo1f

qvk1mo1f2#

https://golang.org/cl/179298提到了这个问题:database/sql: run prepare & execute commands on the same conn from DB pool

oxiaedzo

oxiaedzo3#

@nishaad78 我可以为您的Go1.14版本的CL进行审查(我们仍在冻结1.13版本)。
对于您的用例,您是否考虑使用专用的Conn()并在单个Conn上调用prepare和execute?

31moq8wy

31moq8wy4#

是的,我们打算为我们的用例切换到专用连接。然而,我们仍然认为这种行为应该修复,而不需要使用一个,因为Prepare()可以在数据库池上运行...。

发件人:Daniel Theophanes notifications@github.com
发送时间:星期四,2019年5月30日,上午2:31:57
收件人:golang/go
抄送:Nishaad Ajani;提及主题:关于在具有多个打开连接的数据库池上执行语句时,数据库/sql的prepare命令总是再次发送的问题(#32298) @nishaad78< https://github.com/nishaad78 > 我可以查看Go1.14的CL(我们仍在1.13冻结中)。对于您的用例,您是否考虑使用专用Conn()并在一个Conn上调用prepare和execute? — 您收到此邮件是因为有人提到了您。直接回复此电子邮件,在GitHub上查看<#32298?email_source=notifications&email_token=AB7ZYNLVOXQTWMVKIXYSYTTPX3DZ3A5CNFSM4HQI6NUKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWQHQ2Q#issuecomment-497055850>,或者静音线程< https://github.com/notifications/unsubscribe-auth/AB7ZYNIYC6ADB3BHBAOSUWTPX3DZ3ANCNFSM4HQI6NUA >。

vi4fp9gy

vi4fp9gy5#

@kardianos 1.12版本的这个是否可以作为小版本?

gmxoilav

gmxoilav6#

@nishaad78 假设它已经合并,不,它仍然不会符合小版本发布的要求。

hgtggwj0

hgtggwj07#

请在大型(64+核心)机器上测试可扩展性。

参见$x_{1e0f1}x$和$x_{1e1f1}x$

js4nwp54

js4nwp548#

@nishaad78 我很高兴你选择转向专用连接。在整个SQL包代码库中,我认为语句子池是最具有挑战性的。你的CL可能没问题,但我高度怀疑我能否合并或维护它。
编辑:澄清一下,除非提出一个更简单、非常直接的解决方案,否则这很可能会被拒绝。

相关问题