Go包”database/sql”在使用相同的SQL查询时得到了不同的差异结果。

huangapple go评论154阅读模式
英文:

Go package "database/sql" got the diff-result when use the same SQL-query

问题

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, _ := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")

	// Just Query
	rows, _ := db.Query("SELECT id FROM test_1 WHERE id=123456")
	for rows.Next() {
		var id interface{}
		rows.Scan(&id)
		fmt.Println("Query no args =>", id)
	}

	// Query with args
	rows, _ = db.Query("SELECT id FROM test_1 WHERE id=?", 123456)
	for rows.Next() {
		var id interface{}
		rows.Scan(&id)
		fmt.Println("Query has args =>", id)
	}
}

输出:

$ go run main.go
Query no args => [49 50 51 52 53 54]
Query has args => 123456

问题:

在我看来,"SELECT id FROM test_1 WHERE id=123456""SELECT id FROM test_1 WHERE id=?" 是相同的 SQL 查询语句。

为什么结果的类型不同?

这是一个 bug 还是只是不友好的 API 设计?

英文:
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, _ := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")

    // Just Query
    rows, _ := db.Query("SELECT id FROM test_1 WHERE id=123456")
    for rows.Next() {
        var id interface{}
        rows.Scan(&id)
        fmt.Println("Query no args =>", id)
    }

    // Query with args
    rows, _ = db.Query("SELECT id FROM test_1 WHERE id=?", 123456)
    for rows.Next() {
        var id interface{}
        rows.Scan(&id)
        fmt.Println("Query has args =>", id)
    }
}

Output:

$ go run main.go
Query no args => [49 50 51 52 53 54]
Query has args => 123456

Question:

In my mind, "SELECT id FROM test_1 WHERE id=123456" and "SELECT id FROM test_1 WHERE id=?", 123456 are same SQL-query.

Why the result's TYPE is not same?

Is it a bug or just unfriendly API design?

答案1

得分: 1

这与问题#366有关。MySQL中有两种协议:

  1. 文本协议。使用文本协议时,无论列的类型如何,查询结果都将以[]byte的形式存储。相关的源代码可以在textRows.readRow方法中找到。进一步的转换将在rows.Scan中进行,这是database/sql包的一部分。在这里,结果将从[]byte转换(如果可转换)为传递给Scan的参数类型。
  2. 较新的二进制协议。使用此协议时,查询结果将根据列的类型转换为适当的类型。相关的源代码可以在binaryRows.readRow中找到。

在这个问题中,由于Scan参数的类型是interface{},在database/sql方面,不会进行任何转换。当发出一个没有参数的查询时,驱动程序似乎会使用文本协议,但是当查询有参数时,驱动程序将创建预处理语句,然后使用二进制协议与服务器通信。简而言之:

  1. 没有参数的查询:文本协议 → 结果为[]byte → 结果被扫描为interface{} → 结果作为123456的ASCII码返回(即[49 50 51 52 53 54])。
  2. 带有参数的查询:二进制协议 → 根据列类型(int64)进行转换 → 结果被扫描为interface{} → 结果作为int64返回。

如果你想获得相同的结果,可以使用以下代码进行第一个查询(为了清晰起见,省略了错误处理):

//注释掉
//rows, _ := db.Query("SELECT id FROM test_1 WHERE id=123456")

//然后替换为
stmt, _ := db.Prepare("SELECT id FROM test_1 WHERE id=123456")
defer stmt.Close()
rows, _ := stmt.Query()
英文:

This is related to issue #366. There are two protocols in MySQL:

  1. Text protocol. When text protocol is used, regardless of column's type, the query result will be stored as []byte. Related source code can be found at method textRows.readRow. Further conversion will be done during rows.Scan which is part of database/sql package. Here, the result will be converted (if convertible) from []byte to the type of argument being passed to Scan.
  2. Newer binary protocol. When this protocol is used, the query result will be converted to appropriate type based on column's type. Related source code can be found at binaryRows.readRow.

In this question, since the type of Scan argument is interface{}, in database/sql side, no conversion will occurs. When issuing a query without args, it seems that the driver will used text protocol, but when the query has argument(s), the driver will create prepared-statement then communicate to the server using binary protocol. In short:

  1. Query no args: text protocol → result in []byte → result scanned to interface{} → result is returned as ASCII code of 123456 (i.e. [49 50 51 52 53 54]).
  2. Query with args: binary protocol → conversion based on column type (int64) → result scanned to interface{} → result returned as int64.

If you want to get same result, use the following code for first query (for clarity, error handling is omitted):

//Comment out
//rows, _ := db.Query("SELECT id FROM test_1 WHERE id=123456")

//then replace with
stmt, _ := db.Prepare("SELECT id FROM test_1 WHERE id=123456")
defer stmt.Close()
rows, _ := stmt.Query()

huangapple
  • 本文由 发表于 2017年8月29日 18:28:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/45936745.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定