golang操作数据库

go官方提供了database package,包含sql和sql/driver两个包,定义了操作数据库的接口。
而连接数据库的driver需要依赖第三方包

数据包

1
2
3
4
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)

数据库的连接与关闭

sql.Open()不建立与数据库的任何连接,也不会验证驱动连接参数。相反,它只是准备数据库抽象以供以后使用.
函数原型如下,返回一个sql.DB指针
func Open(driverName, dataSourceName string) (*DB, error)

  • driverName: 使用的驱动名. 这个名字其实就是数据库驱动注册到 database/sql 时所使用的名字.
  • dataSourceName: 数据库连接信息,这个连接包含了数据库的用户名, 密码, 数据库主机以及需要连接的数据库名等信息.
    1
    2
    3
    4
    5
    6
    7
    8
    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.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open和Close

数据库操作

Prepare()

预编译语句,返回一个Stmt,Stmt对象可以执行Exec()、Query()、QueryRow()等操作
使用该方法的优势有

  • 可以实现自定义参数的查询
  • 比手动拼接字符串 SQL 语句高效
  • 可以防止SQL注入攻击
    自定义参数的占位符为:在MySql中,参数占位符为?;PostgreSql中为$N,其中N为数字;SQLite接受这两者之一;在Oracle中占位符以冒号开始,并命名为:param1

Exec()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

Query()

执行 SQL 语句

1
2
3
4
5
6
7
8
9
db, err := sql.Open("mysql", "jesse:jesse@tcp(127.0.0.1:3306)/?charset=utf8") 
checkErr(err)
db.Query("drop database if exists tmpdb")
db.Query("create database tmpdb")
db.Query("use tmpdb")
db.Query("create table tmpdb.tmptab(c1 int, c2 varchar(20), c3 varchar(20))")
db.Query("insert into tmpdb.tmptab values(101, '姓名1', 'address1'), (102, '姓名2', 'address2'), (103, '姓名3', 'address3'), (104, '姓名4', 'address4')")
db.Query("delete from tmpdb.tmptab where c1 = 101")
db.Close()

对查询操作

  1. 调用 db.Query 执行 SQL 语句,返回一个 Rows 作为查询的结果
  2. 通过 rows.Next() 迭代查询数据
  3. 通过 rows.Scan() 读取查询到每一行数据
  4. 调用 db.Close() 关闭查询
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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)
    }

用defer内置函数推迟了rows.Close()的执行
每次db.Query操作后, 都建议调用rows.Close(). 因为 db.Query() 会从数据库连接池中获取一个连接, 这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但如果提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭, 则此连接会一直被占用. 因此通常我们使用 defer rows.Close() 来确保数据库连接可以正确放回到连接池中

QueryRow()

单行查询

1
2
3
4
5
6
var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(name)

与Prepare()一起使用

1
2
3
4
5
6
7
8
9
10
stmt, err := db.Prepare("select name from users where id = ?")
if err != nil {
log.Fatal(err)
}
var name string
err = stmt.QueryRow(1).Scan(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(name)