英文:
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中有两种协议:
- 文本协议。使用文本协议时,无论列的类型如何,查询结果都将以
[]byte的形式存储。相关的源代码可以在textRows.readRow方法中找到。进一步的转换将在rows.Scan中进行,这是database/sql包的一部分。在这里,结果将从[]byte转换(如果可转换)为传递给Scan的参数类型。 - 较新的二进制协议。使用此协议时,查询结果将根据列的类型转换为适当的类型。相关的源代码可以在
binaryRows.readRow中找到。
在这个问题中,由于Scan参数的类型是interface{},在database/sql方面,不会进行任何转换。当发出一个没有参数的查询时,驱动程序似乎会使用文本协议,但是当查询有参数时,驱动程序将创建预处理语句,然后使用二进制协议与服务器通信。简而言之:
- 没有参数的查询:文本协议 → 结果为
[]byte→ 结果被扫描为interface{}→ 结果作为123456的ASCII码返回(即[49 50 51 52 53 54])。 - 带有参数的查询:二进制协议 → 根据列类型(
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:
- 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 methodtextRows.readRow. Further conversion will be done duringrows.Scanwhich is part ofdatabase/sqlpackage. Here, the result will be converted (if convertible) from[]byteto the type of argument being passed toScan. - 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:
- Query no args: text protocol → result in
[]byte→ result scanned tointerface{}→ result is returned as ASCII code of123456(i.e.[49 50 51 52 53 54]). - Query with args: binary protocol → conversion based on column type (
int64) → result scanned tointerface{}→ result returned asint64.
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()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论