在做项目的过程中,遇到一个需求,就是循环从sql数据库中获取符合条件的数据。
rows, err := conn.Query( `select id, content, fav_count from table where quality is null limit ?, ?`, start, count )
if err != nil {
log.Fatal("Query failed:", err.Error())
}
如上,由于需要返回内容,选择了query方法。
先看一下解决方案:
for ; ; {
//通过Statement执行查询
rows, err := conn.Query( `select id, content, fav_count from table where quality is null limit 0, 30`)
if err != nil {
log.Fatal("Query failed:", err.Error())
}
count := 0
for rows.Next() {
count++
println("count=", count)
var (
id int64
content string
fav_count int
)
if err = rows.Scan(&id, &content, &fav_count); err != nil {
log.Fatal(err)
}
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
defer rows.Close()
if (count < 30) {
break
}
}
如上,采用在遍历结果集中计数来判定是否结束select循环体。
由于,没有找到原生的计数count,而引发的 query 的学习:
/*
执行一个查询并返回多个数据行, 这个查询通常是一个 SELECT 。 方法的 arg 部分用于填写查询语句中包含的占位符的实际参数。
*/
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
type Queryer interface {
Query(query string, args []Value) (Rows, error)
}
Rows is an iterator over an executed query's results.
type Rows interface {
// Columns returns the names of the columns. The number of
// columns of the result is inferred from the length of the
// slice. If a particular column name isn't known, an empty
// string should be returned for that entry.
Columns() []string
// Close closes the rows iterator.
Close() error
// Next is called to populate the next row of data into
// the provided slice. The provided slice will be the same
// size as the Columns() are wide.
//
// Next should return io.EOF when there are no more rows.
//
// The dest should not be written to outside of Next. Care
// should be taken when closing Rows not to modify
// a buffer held in dest.
Next(dest []Value) error
}
从这里开始就是query的源码:
//这里是query函数的源码
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
return stmt.query(args)
}
func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
if stmt.mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := stmt.writeExecutePacket(args)
if err != nil {
return nil, stmt.mc.markBadConn(err)
}
mc := stmt.mc
// Read Result
resLen, err := mc.readResultSetHeaderPacket()
if err != nil {
return nil, err
}
rows := new(binaryRows)
if resLen > 0 {
rows.mc = mc
//实际上他在返回的时候只返回了字段的名字,而不是所有内容
rows.rs.columns, err = mc.readColumns(resLen)
} else {
rows.rs.done = true
switch err := rows.NextResultSet(); err {
case nil, io.EOF:
return rows, nil
default:
return nil, err
}
}
return rows, err
}
//这里再贴一下 readResultSetHeaderPacket 的源码
func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
data, err := mc.readPacket()
if err == nil {
switch data[0] {
case iOK:
return 0, mc.handleOkPacket(data)
case iERR:
return 0, mc.handleErrorPacket(data)
case iLocalInFile:
return 0, mc.handleInFileRequest(string(data[1:]))
}
// column count
num, _, n := readLengthEncodedInteger(data)
if n-len(data) == 0 {
return int(num), nil
}
return 0, ErrMalformPkt
}
return 0, err
}
type mysqlConn struct {
buf buffer //我猜测实际的内容是存在这里的,通过next获取这个buff的内容
netConn net.Conn
rawConn net.Conn // underlying connection when netConn is TLS connection.
affectedRows uint64
insertId uint64
cfg *Config
maxAllowedPacket int
maxWriteSize int
writeTimeout time.Duration
flags clientFlag
status statusFlag
sequence uint8
parseTime bool
reset bool // set when the Go SQL package calls ResetSession
// for context support (Go 1.8+)
watching bool
watcher chan<- context.Context
closech chan struct{}
finished chan<- struct{}
canceled atomicError // set non-nil if conn is canceled
closed atomicBool // set when conn is closed, before closech is closed
}
type resultSet struct {
columns []mysqlField //字段的详细信息
columnNames []string
done bool
}
type mysqlRows struct {
mc *mysqlConn
rs resultSet
finish func()
}
//query中new的类型
type textRows struct {
mysqlRows
}
上面贴出了,query函数的源码实现,重要的地方已经给出注释。
这里,我们也得出结论:
Query返回的时候只是取出了字段信息,真实的数据库记录还留在buffer中,for循环Next,每次从buffer中读取一条记录,存在rows结构体的lastcols字段中,调用Scan的时候就是从lastcols取出值。
最后备注一下:
将sql的参数传入Query方法是防止sql注入,防注入是驱动通过转义特殊字符来实现的。