我尝试在Go应用程序中使用Scanner对象来查询数据库服务器。我使用Microsoft SQL Server没有问题,但发现如果我使用带有Rows.Scan
的scanner对象来查询MySQL,则src
参数对于所有类型都是[]uint8
。
我的table是
CREATE TABLE IF NOT EXISTS fruits (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(45),
year SMALLINT,
amount INT,
price FLOAT(23),
PRIMARY KEY (id)
) ENGINE=InnoDB CHARSET=utf8mb4;
字符串
这是我的代码
type Scanner struct {
FieldName string
}
func (s *Scanner) Scan(src any) error {
fmt.Printf("FieldName: %s, src: type=%[2]T value=%[2]v", s.FieldName, src)
return nil
}
// prepare to query
id_scanner := &Scanner{"Id"}
name_scanner := &Scanner{"Name"}
year_scanner := &Scanner{"Year"}
amount_scanner := &Scanner{"Amount"}
price_scanner := &Scanner{"Price"}
// query single row
row := db.QueryRow("select * from fruits where id=?", 1)
err := row.Scan(id_scanner, name_scanner, year_scanner, amount_scanner, price_scanner)
型err
是nil
,输出看起来非常好,例如
FieldName: Id, src: type=int64 value=1
FieldName: Name, src: type=[]uint8 value=[97 112 112 108 101]
FieldName: Year, src: type=int64 value=1965
FieldName: Amount, src: type=int64 value=123
FieldName: Price, src: type=float32 value=7.55
型
这表明Row.Scan
可以与Scanner对象一起工作。但是,Rows.Scan
会失败:
rows, err := db.Query("select * from fruits;")
// err is nil
defer rows.Close()
for rows.Next() {
err := rows.Scan(id_scanner, name_scanner, year_scanner, amount_scanner, price_scanner)
// err is nil, but output is wrong
}
型
在mysql:8..0.31 docker镜像上运行,输出是不同的:
FieldName: Id, src: type=[]uint8 value=[49]
FieldName: Name, src: type=[]uint8 value=[97 112 112 108 101]
FieldName: Year, src: type=[]uint8 value=[49 57 54 53]
FieldName: Amount, src: type=[]uint8 value=[49 50 51]
FieldName: Price, src: type=[]uint8 value=[55 46 53 53]
型
- 对于
Id
字段,值[49]
是字符串“1”。 - 对于
Name
字段,值[97 112 112 108 101]
是字符串“apple”。 - 对于
Year
字段,值[49 57 54 53]
是字符串“1965”。 - 对于
Amount
字段,值[49 50 51]
是字符串“123”。 - 对于
Price
字段,值[55 46 53 53]
是字符串“7.55”。
看起来src
参数是fmt.Print
的结果,如果涉及到Scanner对象,则类似。
我希望看到Rows.Scan
产生与Row.Scan
相同的输出。
有谁知道我哪里出错了吗?为什么代码可以在Microsoft SQL Server中工作,但不能在MySQL中工作?
1条答案
按热度按时间ljsrvy3e1#
我在mysql 8.0.34和
github.com/go-sql-driver/mysql
驱动程序上重现了您的问题。我还检查了其他数据库引擎不会在如此简单的情况下发生-在我的情况下postgres 14.x。似乎不应该发生,因为Row.Scan的文档说扫描与Rows. Scan的工作原理相同。确实令人困惑。但是,我认为您的实验可能与
database/sql
API的典型用法无关(下面将详细介绍)。您似乎正在修补
sql.Scanner
接口,以了解它是如何工作的。您已经观察到,当接收器获取src
参数时,它已经示例化为正确类型的值,或者它只是字节流(但正确的字节流)当它被不同地调用时。在实践中,这似乎不是一个问题。首先,为列实现自己的sql.Scanner
并不常见。即使这样做,我注意到有一些神秘的情况,当我收到字节时,我需要相应地解析它们。(我在postgres上的自定义类型发生了这种情况,这并不是Query
和QueryRows
之间的区别-事实上,我没有深入到隔离差异的程度)最后我使用了一个类型开关,处理[]byte
和预期的具体类型,例如:字符串
在为自己的类型实现列扫描器时,您可能会使用相同的策略。
请记住,你通常只是将
Scan
放入你的结构体字段,而不考虑自定义扫描。下面的代码适用于Row.Scan
和Rows.Scan
:型
结果是:
型
希望这对你有帮助!
请注意,我使用了一些简化,将
Year
作为字符串。实际上,它可以是各种类型,自定义日期,time.Time
截断为年份,等等。它似乎与您的情况无关。你可能听说过将货币存储为float是一个糟糕的想法-请使用mysql DECIMAL(M,2)和Go端的shopspring/decimal。
PS3.你的
Scan
实现中的fmt.Printf
在格式参数的编号方面有一个小错误,对于给定的两个参数,它应该是... type=%[2]T value=%[2]v
。我猜你只是从你的编码的其他时刻错误地粘贴代码。