我在执行查询时遇到了一些非常奇怪的不一致,并且想知道是否有人知道为什么。
想象一下,我有一个结构定义如下:
type Result struct { Afield string `db:"A"` Bfield interface{} `db:"B"` Cfield string `db:"C"` Dfield string `db:"D"` }
以及具有以下列的MySQL表:
A : VARCHAR(50) B : INT C : VARCHAR(50) D : VARCHAR(50)
我想执行的查询:
从表WHERE A =“ a”中选择A,B,C,D
可以执行的第一种方式:
db.Get(&result, `SELECT A, B, C, D FROM table WHERE A="a"`)
第二种可以执行的方式:
db.Get(&result, `SELECT A, B, C, D FROM table WHERE A=?`, "a")
我遇到的不一致情况如下:以第一种方式执行查询时,Bfield的类型为int。但是,第二次执行查询时为[]uint8。
int
[]uint8
例如,当B为1时,就会出现这种结果。
为什么Bfield的类型根据查询的执行方式而不同?
连接声明:
// Connection is an interface for making queries. type Connection interface { Exec(query string, args ...interface{}) (sql.Result, error) Get(dest interface{}, query string, args ...interface{}) error Select(dest interface{}, query string, args ...interface{}) error }
编辑
使用Go数据库/ sql软件包+驱动程序也会发生这种情况。以下查询分别分配Bfield给[]uint8和int64。
Bfield
int64
db的类型为* sql.DB
查询1:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A="a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
- >类型的BfieldIS[]uint8
查询2:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A=?, "a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
- >类型的BfieldISint64
其他注意事项,当链接多个WHERE子句时,只要使用填充 至少 1个?,查询将返回int。否则,如果它们都填充在字符串中,它将返回[]uint8
?
简短的答案:因为MySQL驱动程序对带有参数和不带有参数的查询使用不同的协议。使用准备好的语句以获得一致的结果。
以下说明引用了标准MySQL驱动程序github.com/go-sql-driver/mysql 1.4版
在第一种情况下,驱动程序将查询直接发送到MySQL,并将结果解释为*textRows结构。这个结构(几乎)总是将结果解码为字节片,并将转换结果转换为更好的类型给Go sql包。这工作正常,如果目标是int,string,sql.Scanner等,但不适合interface{}。
*textRows
sql
string
sql.Scanner
interface{}
在第二种情况下,驱动程序检测到有参数并返回driver.ErrSkip。这将导致Go SQL包使用PreparedStatement。在这种情况下,MySQL驱动程序使用*binaryRows结构来解释结果。此结构使用声明的列类型(INT在这种情况下)解码值,在这种情况下将值解码为int64。
driver.ErrSkip
*binaryRows
INT
有趣的事实:如果将interpolateParams=true参数提供给数据库DSN(例如"root:testing@/mysql?interpolateParams=true"),则MySQL驱动程序将在客户端准备查询,而不使用PreparedStatement。此时,两种查询的行为都相同。
interpolateParams=true
"root:testing@/mysql?interpolateParams=true"
一个小的概念证明:
package main import ( "database/sql" "log" _ "github.com/go-sql-driver/mysql" ) type Result struct { Afield string Bfield interface{} } func main() { db, err := sql.Open("mysql", "root:testing@/mysql") if err != nil { log.Fatal(err) } defer db.Close() if _, err = db.Exec(`CREATE TABLE IF NOT EXISTS mytable(A VARCHAR(50), B INT);`); err != nil { log.Fatal(err) } if _, err = db.Exec(`DELETE FROM mytable`); err != nil { log.Fatal(err) } if _, err = db.Exec(`INSERT INTO mytable(A, B) VALUES ('a', 3)`); err != nil { log.Fatal(err) } var ( usingLiteral Result usingParam Result usingLiteralPrepared Result ) row := db.QueryRow(`SELECT B FROM mytable WHERE A='a'`) if err := row.Scan(&usingLiteral.Bfield); err != nil { log.Fatal(err) } row = db.QueryRow(`SELECT B FROM mytable WHERE A=?`, "a") if err := row.Scan(&usingParam.Bfield); err != nil { log.Fatal(err) } stmt, err := db.Prepare(`SELECT B FROM mytable WHERE A='a'`) if err != nil { log.Fatal(err) } defer stmt.Close() row = stmt.QueryRow() if err := row.Scan(&usingLiteralPrepared.Bfield); err != nil { log.Fatal(err) } log.Printf("Type when using literal: %T", usingLiteral.Bfield) // []uint8 log.Printf("Type when using param: %T", usingParam.Bfield) // int64 log.Printf("Type when using prepared: %T", usingLiteralPrepared.Bfield) // int64 }