一、导入库
import (
"database/sql"
_ "github.com/lib/pq"
)
二、连接DB
func main() {
db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")
/*db, err := sql.Open("postgres",
"postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full")*/
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
sql.Open的第一个参数是driver名称,第二个参数是driver连接数据库的信息。DB不是连接,并且只有当需要使用时才会创建连接,如果想立即验证连接,需要用Ping()方法,如下:
err = db.Ping()
if err != nil {
// do something here
}
sql.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。
在database/sql中有一个很基本的连接池,当需要连接,且连接池中没有可用连接时,新的连接就会被创建;如果长时间保持空闲连接,可能会导致db timeout。可以设置SetMaxIdleConns和SetMaxOpenConns,也就是最大空闲连接和最大连接数,分别是下面这两个函数:
db.SetMaxIdleConns(n)
db.SetMaxOpenConns(n)
三、查询DB
(1)一般查询Query
var name, sex string
rows, err := db.Query("select name, sex from user where id = $1 ", 1)
if err != nil {
fmt.Println(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&name, &sex)
if err != nil {
fmt.Println(err)
}
}
err = rows.Err()
if err != nil {
fmt.Println(err)
}
fmt.Println("name:", name, "sex:", sex)
上面代码的过程为:db.Query()表示向数据库发送一个query,defer rows.Close()非常重要(关闭连接),遍历rows使用rows.Next(),把遍历到的数据存入变量使用rows.Scan(),遍历完成后检查error。有几点需要注意:
(1) 检查遍历是否有error
(2) 结果集(rows)未关闭前,底层的连接处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但是如果提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭。所以手动关闭非常重要。rows.Close()可以多次调用,是无害操作。
(2)单条查询QueryRow
var name string
err = db.QueryRow("select name from user where id = $1", 222).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)
四、增删改Exec
Prepared Statements and Connection
在数据库层面,Prepared Statements是和单个数据库连接绑定的。客户端发送一个有占位符的statement到服务端,服务器返回一个statement ID,然后客户端发送ID和参数来执行statement。
在GO中,连接不直接暴露,你不能为连接绑定statement,而是只能为DB或Tx绑定。database/sql包有自动重试等功能。当你生成一个Prepared Statement
(1)自动在连接池中绑定到一个空闲连接
(2)Stmt对象记住绑定了哪个连接
(3)执行Stmt时,尝试使用该连接。如果不可用,例如连接被关闭或繁忙中,会自动re-prepare,绑定到另一个连接。
这就导致在高并发的场景,过度使用statement可能导致statement泄漏,statement持续重复prepare和re-prepare的过程,甚至会达到服务器端statement数量上限。
有些场景不适合用statement:
(1)数据库不支持。例如Sphinx,MemSQL。他们支持MySQL wire protocol, 但不支持"binary" protocol。
(2)statement不需要重用很多次,并且有其他方法保证安全。
stmt, err := db.Prepare("insert into user(name, sex)values($1,$2)")
if err != nil {
fmt.Println(err)
}
rs, err := stmt.Exec("go-test", 12)
if err != nil {
fmt.Println(err)
}
//可以获得影响行数
affect, err := rs.RowsAffected()
fmt.Println("affect ", affect ," rows")
五、事务
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
defer tx.Rollback()
stmt, err := tx.Prepare("INSERT INTO foo VALUES ($1)")
if err != nil {
log.Fatal(err)
}
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)
}
defer stmt.Close() //runs here!
db.Begin()开始事务,Commit() 或 Rollback()关闭事务。Tx从连接池中取出一个连接,在关闭之前都是使用这个连接。Tx不能和DB层的BEGIN, COMMIT混合使用。
有疑问加站长微信联系(非本文作者)