上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
prepare
所谓 prepare statment
,是指数据库对 SQL 进行预处理(检查、优化、编译等)。
优点:
- 占位符加参数方式更方便,可防止 SQL 注入等问题。
- 某些数据库使用二进制协议。相比文本,效率更高。
- 对于多次执行,消除了解析等开销,性能更好。
缺点:
- 首次预处理时间较长。
- 多次网络通讯开销(
prepare
/exec
/close
)。
分客户端和服务器两种实现方式。
占位符 :MySQL
?
,PostgreSQL
$1 $2
,SQLite
?
OR$1
。
- 某些驱动,将含参数操作一律当
Prepare
执行。 - 不含参数的
Simple Exec
,可能有更好的性能。 - 应及时调用
Stmt.Close
释放资源。
package main import ( "database/sql" "fmt" "log" _ "github.com/mattn/go-sqlite3" ) func main() { log.SetFlags(log.Lshortfile) db, err := sql.Open("sqlite3", "./test.db") if err != nil { log.Fatalln(err) } defer db.Close() // ------------------ q := `INSERT INTO user (id, name) VALUES (?, ?)` stmt, err := db.Prepare(q) if err != nil { log.Fatalln(err) } for i := 0; i < 10; i++ { result, err := stmt.Exec(i, fmt.Sprintf("u%d", i)) if err != nil { log.Fatalln(err) } fmt.Println(result.RowsAffected()) } // 关闭,并释放资源! if err := stmt.Close(); err != nil { log.Fatalln(err) } }
源码剖析
虽然 Prepare
退出前调用 releaseConn
归还了连接,但 Stmt.css
依旧持有该连接引用。
因为 Prepare
总是和特定连接相绑定。
// database/sql/sql.go func (db *DB) prepare(ctx, query, strategy) (*Stmt, error) { dc, err := db.conn(ctx, strategy) return db.prepareDC(ctx, dc, dc.releaseConn, nil, query) }
func (db *DB) prepareDC(ctx, dc, release, cg, query) (*Stmt, error) { // 释放连接。 defer func() { release(err) }() // 执行! withLock(dc, func() { ds, err = dc.prepareLocked(ctx, cg, query) }) if err != nil { return nil, err } stmt := &Stmt{ db: db, query: query, cg: cg, cgds: ds, } // 继续持有连接引用! // cg != nil --> transaction!!! if cg == nil { stmt.css = []connStmt{{dc, ds}} db.addDep(stmt, stmt) } return stmt, nil }
当执行 Stmt.Exec
、 Query
等操作时,会尝试从连接池中获取 “记忆” 连接。
获取失败,则在新连接上重新执行 Prepare
操作。新连接也会被释放。
func (s *Stmt) ExecContext(ctx context.Context, args ...any) (Result, error) { for i := 0; i < maxBadConnRetries+1; i++ { // 找到记忆连接,或新建。 dc, releaseConn, ds, err := s.connStmt(ctx, strategy) ... // 执行,释放连接。 res, err = resultFromStatement(ctx, dc.ci, ds, args...) releaseConn(err) if !errors.Is(err, driver.ErrBadConn) { return res, err } } return nil, driver.ErrBadConn }
func (s *Stmt) connStmt(ctx, strategy) (dc, releaseConn, ds, err error) { // 从池取一个连接。 dc, err = s.db.conn(ctx, strategy) if err != nil { return nil, nil, nil, err } // 判断是否记忆连接。 for _, v := range s.css { if v.dc == dc { return dc, dc.releaseConn, v.ds, nil } } // 重新执行 Prepare,并记下新连接。 withLock(dc, func() { ds, err = s.prepareOnConnLocked(ctx, dc) }) return dc, dc.releaseConn, ds, nil }
清理
执行完毕,如何清理 Stmt.css
资源?毕竟数据库保存了这些连接的 Prepare
状态。
首先,在 prepareDC
尾部有 db.addDep(stmt, stmt)
调用。
func (db *DB) addDepLocked(x finalCloser, dep any) { if db.dep == nil { db.dep = make(map[finalCloser]depSet) } xdep := db.dep[x] if xdep == nil { xdep = make(depSet) db.dep[x] = xdep } xdep[dep] = true }
当 Stmt.Close
被调用,会移除这个设置,并调用 finalCloser
。
func (s *Stmt) Close() error { if s.cg == nil { return s.db.removeDep(s, s) } return txds.Close() }
func (db *DB) removeDep(x finalCloser, dep any) error { fn := db.removeDepLocked(x, dep) return fn() } func (db *DB) removeDepLocked(x finalCloser, dep any) func() error { switch len(xdep) { case 0: // No more dependencies. delete(db.dep, x) return x.finalClose } }
很显然,这对应 Stmt.finalClose
方法,通知驱动程序关闭相关资源。
func (s *Stmt) finalClose() error { if s.css != nil { for _, v := range s.css { s.db.noteUnusedDriverStatement(v.dc, v.ds) v.dc.removeOpenStmt(v.ds) } s.css = nil } return nil }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论