sql - Go SQL 查询不一致
问题描述
我在执行查询时遇到了一些非常奇怪的不一致,并且想知道是否有人知道原因。
想象一下,我有一个定义如下的结构:
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)
我要执行的查询:
从表中选择 A、B、C、D,其中 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 数据库/sql 包 + 驱动程序也会发生这种情况。下面的查询分别分配Bfield
给[]uint8
和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)
--> 类型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 ,查询就会返回int
。否则,如果它们都填充在字符串中,它将返回[]uint8
解决方案
简短的回答:因为 MySQL 驱动程序对带参数和不带参数的查询使用不同的协议。使用准备好的语句来获得一致的结果。
以下解释参考标准 MySQL 驱动github.com/go-sql-driver/mysql,版本 1.4
在第一种情况下,驱动程序将查询直接发送到 MySQL,并将结果解释为*textRows
结构。这个结构(几乎)总是将结果解码为一个字节切片,并将转换为更好的类型留给 Gosql
包。int
如果目的地是,等,这很好用string
,sql.Scanner
但不是interface{}
.
在第二种情况下,驱动程序检测到有参数并返回driver.ErrSkip
。这会导致 Go SQL 包使用 PreparedStatement。在这种情况下,MySQL 驱动程序使用*binaryRows
结构来解释结果。此结构使用声明的列类型(INT
在本例中)来解码 value,在本例中将 value 解码为int64
。
有趣的事实:如果您将interpolateParams=true
参数提供给数据库 DSN(例如"root:testing@/mysql?interpolateParams=true"
),MySQL 驱动程序将在客户端准备查询,而不使用 PreparedStatement。此时,两种类型的查询行为相同。
一个小的概念证明:
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
}
推荐阅读
- kubernetes - kubernetes 创建有状态集,pod 未启动
- java - 错误:无法在 Netbeans 中找到或加载主类
- javascript - jQuery 搜索/过滤列表
- javascript - 如何截断浮点数精度以使两个相似的浮点数被视为相同
- r - 如何在R中的列表列表中组合矩阵?
- react-native - How to make an React DrawerNavigator item just be a clickable link
- windows - What rights to assign during installation time to avoid 'Unable to write to' error message related to Windows VirutalStore?
- html - Content entered in the CodeMirror editor is not rendering in a preview div correctly
- flutter - Flutter How to remove white blank between sliverappbar and sliverList when pull down sliverlist?
- sql-server - Oracle sqlldr SQL Server new line character is getting loaded