database/sql 中 Query 方法详细解析

在做项目的过程中,遇到一个需求,就是循环从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注入,防注入是驱动通过转义特殊字符来实现的。

你可能感兴趣的:(golang)