由于最近在学习Go,因此找了revel这个框架来学习,感受和php的面向对象有很大不一样。revel没有提供db mapping的组件,因此在github上搜了不少ORM来学习,在
jmoiron/sqlx
中发现了一篇比较详细介绍database/sql
这个包的文章,拿来和你们分享。本文并非按字句的翻译,若是哪里表述不清楚建议阅读原文 原文地址php
sql.DB
不是一个链接,它是数据库的抽象接口。它能够根据driver打开关闭数据库链接,管理链接池。正在使用的链接被标记为繁忙,用完后回到链接池等待下次使用。因此,若是你没有把链接释放回链接池,会致使过多链接使系统资源耗尽。html
这里使用的是MySQL driversmysql
import ( "database/sql" _ "github.com/go-sql-driver/mysql" )
func main() { db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello") if err != nil { log.Fatal(err) } defer db.Close() }
sql.Open
的第一个参数是driver名称,第二个参数是driver链接数据库的信息,各个driver可能不一样。DB不是链接,而且只有当须要使用时才会建立链接,若是想当即验证链接,须要用Ping()
方法,以下:git
err = db.Ping() if err != nil { // do something here }
sql.DB的设计就是用来做为长链接使用的。不要频繁Open, Close。比较好的作法是,为每一个不一样的datastore建一个DB对象,保持这些对象Open。若是须要短链接,那么把DB做为参数传入function,而不要在function中Open, Close。程序员
若是方法包含Query
,那么这个方法是用于查询并返回rows的。其余状况应该用Exec()
。github
var ( id int name string ) rows, err := db.Query("select id, name from users where id = ?", 1) if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { err := rows.Scan(&id, &name) if err != nil { log.Fatal(err) } log.Println(id, name) } err = rows.Err() if err != nil { log.Fatal(err) }
上面代码的过程为:db.Query()
表示向数据库发送一个query,defer rows.Close()
很是重要,遍历rows使用rows.Next()
, 把遍历到的数据存入变量使用rows.Scan()
, 遍历完成后检查error。有几点须要注意:sql
rows.Close()
,可是若是提早退出循环,rows不会关闭,链接不会回到链接池中,链接也不会关闭。因此手动关闭很是重要。rows.Close()
能够屡次调用,是无害操做。err在Scan
后才产生,因此能够以下写:数据库
var name string err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) if err != nil { log.Fatal(err) } fmt.Println(name)
通常用Prepared Statements和Exec()
完成INSERT
, UPDATE
, DELETE
操做。安全
stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)") if err != nil { log.Fatal(err) } res, err := stmt.Exec("Dolly") if err != nil { log.Fatal(err) } lastId, err := res.LastInsertId() if err != nil { log.Fatal(err) } rowCnt, err := res.RowsAffected() if err != nil { log.Fatal(err) } log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)
db.Begin()
开始事务,Commit()
或 Rollback()
关闭事务。Tx
从链接池中取出一个链接,在关闭以前都是使用这个链接。Tx不能和DB层的BEGIN
, COMMIT
混合使用。服务器
若是你须要经过多条语句修改链接状态,你必须使用Tx,例如:
SET @var := somevalue
在数据库层面,Prepared Statements是和单个数据库链接绑定的。客户端发送一个有占位符的statement到服务端,服务器返回一个statement ID,而后客户端发送ID和参数来执行statement。
在GO中,链接不直接暴露,你不能为链接绑定statement,而是只能为DB或Tx绑定。database/sql
包有自动重试等功能。当你生成一个Prepared Statement
Stmt
对象记住绑定了哪一个链接Stmt
时,尝试使用该链接。若是不可用,例如链接被关闭或繁忙中,会自动re-prepare,绑定到另外一个链接。这就致使在高并发的场景,过分使用statement可能致使statement泄漏,statement持续重复prepare和re-prepare的过程,甚至会达到服务器端statement数量上限。
某些操做使用了PS,例如db.Query(sql, param1, param2)
, 并在最后自动关闭statement。
有些场景不适合用statement:
PS在Tx中惟一绑定一个链接,不会re-prepare。
Tx和statement不能分离,在DB中建立的statement也不能在Tx中使用,由于他们一定不是使用同一个链接使用Tx必须十分当心,例以下面的代码:
tx, err := db.Begin() if err != nil { log.Fatal(err) } defer tx.Rollback() stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)") if err != nil { log.Fatal(err) } defer stmt.Close() // danger! for i := 0; i < 10; i++ { _, err = stmt.Exec(i) if err != nil { log.Fatal(err) } } err = tx.Commit() if err != nil { log.Fatal(err) } // stmt.Close() runs here!
*sql.Tx
一旦释放,链接就回到链接池中,这里stmt在关闭时就没法找到链接。因此必须在Tx commit或rollback以前关闭statement。
若是循环中发生错误会自动运行rows.Close()
,用rows.Err()
接收这个错误,Close方法能够屡次调用。循环以后判断error是很是必要的。
for rows.Next() { // ... } if err = rows.Err(); err != nil { // handle the error here }
若是你在rows遍历结束以前退出循环,必须手动关闭Resultset,而且接收error。
for rows.Next() { // ... break; // whoops, rows is not closed! memory leak... } // do the usual "if err = rows.Err()" [omitted here]... // it's always safe to [re?]close here: if err = rows.Close(); err != nil { // but what should we do if there's an error? log.Println(err) }
var name string err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) if err != nil { log.Fatal(err) } fmt.Println(name)
若是id为1的不存在,err为sql.ErrNoRows,通常应用中不存在的状况都须要单独处理。此外,Query返回的错误都会延迟到Scan被调用,因此应该写成以下代码:
var name string err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) if err != nil { if err == sql.ErrNoRows { // there were no rows, but otherwise no error occurred } else { log.Fatal(err) } } fmt.Println(name)
把空结果当作Error处理是为了强行让程序员处理结果为空的状况
各个数据库处理方式不太同样,mysql为例:
if driverErr, ok := err.(*mysql.MySQLError); ok { // Now the error number is accessible directly if driverErr.Number == 1045 { // Handle the permission-denied error } }
MySQLError
, Number
都是DB特异的,别的数据库多是别的类型或字段。这里的数字能够替换为常量,例如这个包 MySQL error numbers maintained by VividCortex
简单说就是设计数据库的时候不要出现null,处理起来很是费力。Null的type颇有限,例如没有sql.NullUint64
; null值没有默认零值。
for rows.Next() { var s sql.NullString err := rows.Scan(&s) // check err if s.Valid { // use s.String } else { // NULL value } }
rows.Columns()
的使用,用于处理不能得知结果字段个数或类型的状况,例如:
cols, err := rows.Columns() if err != nil { // handle the error } else { dest := []interface{}{ // Standard MySQL columns new(uint64), // id new(string), // host new(string), // user new(string), // db new(string), // command new(uint32), // time new(string), // state new(string), // info } if len(cols) == 11 { // Percona Server } else if len(cols) > 8 { // Handle this case } err = rows.Scan(dest...) // Work with the values in dest }
cols, err := rows.Columns() // Remember to check err afterwards vals := make([]interface{}, len(cols)) for i, _ := range cols { vals[i] = new(sql.RawBytes) } for rows.Next() { err = rows.Scan(vals...) // Now you can check each element of vals for nil-ness, // and you can use type introspection and type assertions // to fetch the column into a typed variable. }
db.SetMaxIdleConns(N)
设置最大空闲链接数db.SetMaxOpenConns(N)
设置最大打开链接数