为了账号安全,请及时绑定邮箱和手机立即绑定

Go SQL查询不一致

Go SQL查询不一致

Go
翻过高山走不出你 2023-06-12 09:32:11
我在执行查询时遇到了一些非常奇怪的不一致,想知道是否有人知道为什么。假设我有一个定义如下的结构:type Result struct {    Afield string      `db:"A"`    Bfield interface{} `db:"B"`    Cfield string      `db:"C"`    Dfield string      `db:"D"`}以及具有以下列的 MySQL 表:A : VARCHAR(50)B : INTC : VARCHAR(50)D : VARCHAR(50)我要执行的查询:从表中选择 A、B、C、D 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 数据库/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
查看完整描述

2 回答

?
慕尼黑8549860

TA贡献1818条经验 获得超11个赞

简短回答:因为 MySQL 驱动程序对带参数和不带参数的查询使用不同的协议。使用准备好的语句来获得一致的结果。

以下解释参考标准MySQL驱动

在第一种情况下,驱动程序直接将查询发送到 MySQL,并将结果解释为结构*textRows。这个结构(几乎)总是将结果解码为一个字节 slice,并将转换为更好的类型留给 Gosql包。如果目标是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 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

}


查看完整回答
反对 回复 2023-06-12
?
浮云间

TA贡献1829条经验 获得超4个赞

您在 MySql 中的第一个 SQL 字符串是不明确的,

根据 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 返回相同的值,你的问题就解决了。

使用 ` 字符,您传递的是变量名而不是字符串值!


查看完整回答
反对 回复 2023-06-12
  • 2 回答
  • 0 关注
  • 125 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信