【问题标题】:Go SQL query inconsistencyGo SQL 查询不一致
【发布时间】:2019-02-27 00:36:30
【问题描述】:

我在执行查询时遇到了一些非常奇怪的不一致,想知道是否有人知道原因。

假设我有一个定义如下的结构:

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

例如,当 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 分配给[]uint8int64

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

【问题讨论】:

  • 你的意思是Bfield吗?
  • 您使用的是哪个数据库(和驱动程序)包?
  • 我正在导入 Go "database/sql" 包。是的,我为 Bfield 更新了
  • 更正:我正在使用github.com/jmoiron/sqlx驱动程序@leafbebop

标签: sql go sqlx


【解决方案1】:

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

以下解释参考标准MySQL驱动github.com/go-sql-driver/mysql, version 1.4

在第一种情况下,驱动程序将查询直接发送到 MySQL,并将结果解释为 *textRows 结构。这个结构(几乎)总是decodes results into a byte slice,并将转换为更好的类型留给 Go sql 包。如果目的地是intstringsql.Scanner 等,这可以正常工作,但不适用于interface{}

在第二种情况下,the driver detects that there are arguments and returns driver.ErrSkip。这会导致 Go SQL 包使用 PreparedStatement。在这种情况下,MySQL 驱动程序使用*binaryRows 结构来解释结果。 This struct uses the declared column type (INT in this case) to decode the value,在这种情况下将值解码为int64

有趣的事实:如果您向数据库 DSN 提供 interpolateParams=true 参数(例如 "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
}

【讨论】:

  • 感谢您的详细解释!
【解决方案2】:

你的第一个 SQL 字符串,在 MySql 中是模棱两可的,可能有太多的含义,如 StackOverflow 上的解释,在下面的地址

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 返回相同的值,则您的问题已解决。

使用`字符,你传递一个变量名而不是一个字符串值!

【讨论】:

  • 不幸的是,在双引号、反引号和单引号之间更改字符串中使用的引号并不能解决问题。当将 WHERE 子句从检查列匹配字符串时更改为列匹配 int 时也会发生这种情况。
  • 需要注意的一点是,当将换行引号从反引号更改为双引号时,使用第二种方法会返回 int64 而不是之前的 int。但是,第一种方法仍然返回[]uint8
  • 为什么 Result 结构中的 BFIELD 类型是 INTERFACE 而不是 INT ?我不是 GO 专家,但我很惊讶您尝试在 INTERFACE 类型的 GO 对象中读取 INT 数据库变量!
  • 它存储为interface 而不是int,因为有时我需要直接将其设置为nil @schlebe
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-08
  • 1970-01-01
相关资源
最近更新 更多