简短的答案:因为MySQL驱动程序对带有参数和不带有参数的查询使用不同的协议。使用准备好的语句以获得一致的结果。
以下说明引用了标准MySQL驱动程序github.com/go-sql-driver/mysql 1.4版
在第一种情况下,驱动程序将查询直接发送到MySQL,并将结果解释为
*textRows结构。这个结构(几乎)总是将结果解码为字节片,并将转换结果转换为更好的类型给Go
sql包。这工作正常,如果目标是
int,
string,
sql.Scanner等,但不适合
interface{}。在第二种情况下,驱动程序检测到有参数并返回
driver.ErrSkip。这将导致Go
SQL包使用PreparedStatement。在这种情况下,MySQL驱动程序使用
*binaryRows结构来解释结果。此结构使用声明的列类型(
INT在这种情况下)解码值,在这种情况下将值解码为
int64。
有趣的事实:如果将
interpolateParams=true参数提供给数据库DSN(例如
"root:testing@/mysql?interpolateParams=true"),则MySQL驱动程序将在客户端准备查询,而不使用PreparedStatement。此时,两种查询的行为都相同。
一个小的概念证明:
package mainimport ( "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 usingParamResult 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}


