Go SQL 查询不一致
Go SQL query inconsistency
我在执行查询时遇到了一些非常奇怪的不一致,想知道是否有人知道为什么。
假设我有一个定义如下的结构:
type Result struct {
Afield string `db:"A"`
Bfield interface{} `db:"B"`
Cfield string `db:"C"`
Dfield string `db:"D"`
}
还有一个 MySQL Table 具有以下列:
A : VARCHAR(50)
B : INT
C : VARCHAR(50)
D : VARCHAR(50)
我要执行的查询:
SELECT A, B, C, D FROM table WHERE A="a"
第一种执行方式:
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
.
例如当 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 database/sql 包 + 驱动程序也会发生这种情况。下面的查询分别将 Bfield
分配给 []uint8
和 int64
。
db is of type *sql.DB
查询 1:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A="a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
-- > Bfield
的类型是 []uint8
查询 2:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A=?, "a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
--> Bfield
的类型是 int64
编辑
还有一点要注意,当链接多个 WHERE 子句时,只要 至少 1 使用 ?
填充,查询将 return int
。否则如果它们都填充在字符串中,它将 return []uint8
你的第一个 SQL 字符串,在 MySql 中是不明确的,并且可能有太多的含义,正如 Whosebug 在以下地址中所解释的那样
When to use single quotes, double quotes, and back ticks in MySQL
根据 SQL-MODE,您的 SQL 命令可以解释为
SELECT A, B, C, D FROM table WHERE A='a'
这就是我认为你所期待的。
或
SELECT A, B, C, D FROM table WHERE A=`a`
为了避免这种歧义,你能做一个新的 FIRST 测试来用单引号替换双引号吗?
如果相同的行为继续存在,我的回答不是一个好的回应。
如果BOTH SQL select return 相同,你的问题就解决了。
使用 ` 字符,您传递的是变量名而不是字符串值!
简短回答:因为 MySQL 驱动程序对带参数和不带参数的查询使用不同的协议。使用准备好的语句来获得一致的结果。
以下说明参考标准MySQL驱动github.com/go-sql-driver/mysql, version 1.4
在第一种情况下,驱动程序直接将查询发送到 MySQL,并将结果解释为 *textRows
结构。这个结构(几乎)总是 decodes results into a byte slice,并将转换为更好的类型留给 Go sql
包。如果目的地是 int
、string
、sql.Scanner
等,这可以正常工作,但不适用于 interface{}
。
在第二种情况下,the driver detects that there are arguments and returns driver.ErrSkip
. This causes the Go SQL package to use a PreparedStatement. And in that case, the MySQL driver uses a *binaryRows
struct to interpret the results. This struct uses the declared column type (INT
in this case) to decode the value,在这种情况下将值解码为int64
。
有趣的事实:如果您向数据库 DSN(例如 "root:testing@/mysql?interpolateParams=true"
)提供 interpolateParams=true
参数,MySQL 驱动程序将在客户端准备查询,而不是使用准备好的语句。此时,两种类型的查询行为相同。
一个小的概念证明:
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
}
我在执行查询时遇到了一些非常奇怪的不一致,想知道是否有人知道为什么。
假设我有一个定义如下的结构:
type Result struct {
Afield string `db:"A"`
Bfield interface{} `db:"B"`
Cfield string `db:"C"`
Dfield string `db:"D"`
}
还有一个 MySQL Table 具有以下列:
A : VARCHAR(50)
B : INT
C : VARCHAR(50)
D : VARCHAR(50)
我要执行的查询:
SELECT A, B, C, D FROM table WHERE A="a"
第一种执行方式:
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
.
例如当 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 database/sql 包 + 驱动程序也会发生这种情况。下面的查询分别将 Bfield
分配给 []uint8
和 int64
。
db is of type *sql.DB
查询 1:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A="a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
-- > Bfield
的类型是 []uint8
查询 2:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A=?, "a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
--> Bfield
的类型是 int64
编辑
还有一点要注意,当链接多个 WHERE 子句时,只要 至少 1 使用 ?
填充,查询将 return int
。否则如果它们都填充在字符串中,它将 return []uint8
你的第一个 SQL 字符串,在 MySql 中是不明确的,并且可能有太多的含义,正如 Whosebug 在以下地址中所解释的那样
When to use single quotes, double quotes, and back ticks in MySQL
根据 SQL-MODE,您的 SQL 命令可以解释为
SELECT A, B, C, D FROM table WHERE A='a'
这就是我认为你所期待的。
或
SELECT A, B, C, D FROM table WHERE A=`a`
为了避免这种歧义,你能做一个新的 FIRST 测试来用单引号替换双引号吗?
如果相同的行为继续存在,我的回答不是一个好的回应。
如果BOTH SQL select return 相同,你的问题就解决了。
使用 ` 字符,您传递的是变量名而不是字符串值!
简短回答:因为 MySQL 驱动程序对带参数和不带参数的查询使用不同的协议。使用准备好的语句来获得一致的结果。
以下说明参考标准MySQL驱动github.com/go-sql-driver/mysql, version 1.4
在第一种情况下,驱动程序直接将查询发送到 MySQL,并将结果解释为 *textRows
结构。这个结构(几乎)总是 decodes results into a byte slice,并将转换为更好的类型留给 Go sql
包。如果目的地是 int
、string
、sql.Scanner
等,这可以正常工作,但不适用于 interface{}
。
在第二种情况下,the driver detects that there are arguments and returns driver.ErrSkip
. This causes the Go SQL package to use a PreparedStatement. And in that case, the MySQL driver uses a *binaryRows
struct to interpret the results. This struct uses the declared column type (INT
in this case) to decode the value,在这种情况下将值解码为int64
。
有趣的事实:如果您向数据库 DSN(例如 "root:testing@/mysql?interpolateParams=true"
)提供 interpolateParams=true
参数,MySQL 驱动程序将在客户端准备查询,而不是使用准备好的语句。此时,两种类型的查询行为相同。
一个小的概念证明:
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
}