Go 语言如何操作常见的数据库

使用MySQL数据库

目前 Internet 上流行的网站构架方式是 LAMP/LNMP,其中的 M 即 MySQL, 作为数据库MySQL 以免费、开源、使用方便为优势成为了很多Web开发的后端数据库存储引擎。

Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持database/sql标准,而有些是采用了自己的实现接口,常用的有如下几种:

接下来的例子主要以第一个驱动为例,推荐主要理由是:

  • 这个驱动比较新,维护的比较好;
  • 完全支持database/sql接口;
  • 支持keepalive,保持长连接。

接下来的几个小节里面我们都将采用同一个数据库表结构:数据库test,用户表userinfo,关联用户信息表userdetail。

 1CREATE TABLE `userinfo` (
 2    `uid` INT(10) NOT NULL AUTO_INCREMENT,
 3    `username` VARCHAR(64) NULL DEFAULT NULL,
 4    `departname` VARCHAR(64) NULL DEFAULT NULL,
 5    `created` DATE NULL DEFAULT NULL,
 6    PRIMARY KEY (`uid`)
 7);
 8
 9CREATE TABLE `userdetail` (
10    `uid` INT(10) NOT NULL DEFAULT '0',
11    `intro` TEXT NULL,
12    `profile` TEXT NULL,
13    PRIMARY KEY (`uid`)
14)

如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作:

 1// 示例代码11-15
 2package main
 3
 4import (
 5    "database/sql"
 6    "fmt"
 7    //"time"
 8
 9    _ "github.com/go-sql-driver/mysql"
10)
11
12func main() {
13    db, err := sql.Open("mysql", "zuolan:zuolan@/test?charset=utf8")
14    checkErr(err)
15
16    //插入数据
17    stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
18    checkErr(err)
19
20    res, err := stmt.Exec("张三", "研发部门", "2017-09-09")
21    checkErr(err)
22
23    id, err := res.LastInsertId()
24    checkErr(err)
25
26    fmt.Println(id)
27    //更新数据
28    stmt, err = db.Prepare("update userinfo set username=? where uid=?")
29    checkErr(err)
30
31    res, err = stmt.Exec("zuolanupdate", id)
32    checkErr(err)
33
34    affect, err := res.RowsAffected()
35    checkErr(err)
36
37    fmt.Println(affect)
38
39    //查询数据
40    rows, err := db.Query("SELECT * FROM userinfo")
41    checkErr(err)
42
43    for rows.Next() {
44        var uid int
45        var username string
46        var department string
47        var created string
48        err = rows.Scan(&uid, &username, &department, &created)
49        checkErr(err)
50        fmt.Println(uid)
51        fmt.Println(username)
52        fmt.Println(department)
53        fmt.Println(created)
54    }
55
56    //删除数据
57    stmt, err = db.Prepare("delete from userinfo where uid=?")
58    checkErr(err)
59
60    res, err = stmt.Exec(id)
61    checkErr(err)
62
63    affect, err = res.RowsAffected()
64    checkErr(err)
65
66    fmt.Println(affect)
67
68    db.Close()
69
70}
71
72func checkErr(err error) {
73    if err != nil {
74        panic(err)
75    }
76}

通过上面的代码我们可以看出,Go操作MySQL数据库是很方便的。

sql.Open()函数用来打开一个注册过的数据库驱动,go-sql-driver中注册了mysql这个数据库驱动,第二个参数是DSN(Data Source Name),它是go-sql-driver定义的一些数据库链接和配置信息。它支持如下格式:

1user@unix(/path/to/socket)/dbname?charset=utf8
2user:password@tcp(localhost:5555)/dbname?charset=utf8
3user:password@/dbname
4user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

db.Prepare()函数用来返回准备要执行的sql操作,然后返回准备完毕的执行状态。

db.Query()函数用来直接执行Sql返回Rows结果。

stmt.Exec()函数用来执行stmt准备好的SQL语句

我们可以看到我们传入的参数都是=?对应的数据,这样做的方式可以一定程度上防止SQL注入。

使用SQLite数据库

SQLite是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、可靠。与其他数据库管理系统不同,SQLite的安装和运行非常简单,在大多数情况下,只要确保SQLite的二进制文件存在即可开始创建、连接和使用数据库。

如果您正在寻找一个嵌入式数据库项目或解决方案,SQLite是绝对值得考虑。SQLite可以是说开源的Access。

Go支持sqlite的驱动也比较多,但是很多都不支持database/sql接口:

目前支持database/sql的SQLite数据库驱动比较少,采用标准接口有利于以后出现更好的驱动的时候做迁移,因此本节后面都采用第一个驱动。

示例的数据库表结构如下所示,相应的建表SQL:

 1CREATE TABLE `userinfo` (
 2    `uid` INTEGER PRIMARY KEY AUTOINCREMENT,
 3    `username` VARCHAR(64) NULL,
 4    `departname` VARCHAR(64) NULL,
 5    `created` DATE NULL
 6);
 7
 8CREATE TABLE `userdeatail` (
 9    `uid` INT(10) NULL,
10    `intro` TEXT NULL,
11    `profile` TEXT NULL,
12    PRIMARY KEY (`uid`)
13);

看下面Go程序是如何操作数据库表,进行数据增删改查的:

 1// 示例代码11-16
 2package main
 3
 4import (
 5    "database/sql"
 6    "fmt"
 7    "time"
 8
 9    _ "github.com/mattn/go-sqlite3"
10)
11
12func main() {
13    db, err := sql.Open("sqlite3", "./demo.db")
14    checkErr(err)
15
16    //插入数据
17    stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)")
18    checkErr(err)
19
20    res, err := stmt.Exec("张三", "研发部门", "2017-09-09")
21    checkErr(err)
22
23    id, err := res.LastInsertId()
24    checkErr(err)
25
26    fmt.Println(id)
27    //更新数据
28    stmt, err = db.Prepare("update userinfo set username=? where uid=?")
29    checkErr(err)
30
31    res, err = stmt.Exec("zuolanupdate", id)
32    checkErr(err)
33
34    affect, err := res.RowsAffected()
35    checkErr(err)
36
37    fmt.Println(affect)
38
39    //查询数据
40    rows, err := db.Query("SELECT * FROM userinfo")
41    checkErr(err)
42
43    for rows.Next() {
44        var uid int
45        var username string
46        var department string
47        var created time.Time
48        err = rows.Scan(&uid, &username, &department, &created)
49        checkErr(err)
50        fmt.Println(uid)
51        fmt.Println(username)
52        fmt.Println(department)
53        fmt.Println(created)
54    }
55
56    //删除数据
57    stmt, err = db.Prepare("delete from userinfo where uid=?")
58    checkErr(err)
59
60    res, err = stmt.Exec(id)
61    checkErr(err)
62
63    affect, err = res.RowsAffected()
64    checkErr(err)
65
66    fmt.Println(affect)
67
68    db.Close()
69
70}
71
72func checkErr(err error) {
73    if err != nil {
74        panic(err)
75    }
76}

我们可以看到上面的代码和MySQL例子里面的代码几乎是一模一样的,唯一改变的就是导入的驱动改变了,然后调用sql.Open是采用了SQLite的方式打开。

使用PostgreSQL数据库

PostgreSQL是一个自由的对象-关系数据库服务器(数据库管理系统),它在灵活的BSD风格许可证下发行。它提供了相对其他开放源代码数据库系统(比如MySQL和Firebird),和对专有系统比如Oracle、Sybase、IBM的DB2和Microsoft SQL Server的一种选择。

PostgreSQL和MySQL比较,它更加庞大一点,因为它是用来替代Oracle而设计的。所以在企业应用中采用PostgreSQL是一个明智的选择。

MySQL被Oracle收购之后正在逐步的封闭(自MySQL 5.5.31以后的所有版本将不再遵循GPL协议),鉴于此,将来我们也许会选择PostgreSQL而不是MySQL作为项目的后端数据库。

Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发中使用了这个数据库。

在下面的示例中采用第一个驱动,因为它目前使用的人最多,在Github上也比较活跃。

数据库建表语句:

 1CREATE TABLE userinfo
 2(
 3    uid serial NOT NULL,
 4    username character varying(100) NOT NULL,
 5    departname character varying(500) NOT NULL,
 6    Created date,
 7    CONSTRAINT userinfo_pkey PRIMARY KEY (uid)
 8)
 9WITH (OIDS=FALSE);
10
11CREATE TABLE userdeatail
12(
13    uid integer,
14    intro character varying(100),
15    profile character varying(100)
16)
17WITH(OIDS=FALSE);

看下面这个Go如何操作数据库表,进行数据的增删改查:

 1// 示例代码11-17
 2package main
 3
 4import (
 5    "database/sql"
 6    "fmt"
 7
 8    _ "github.com/lib/pq"
 9)
10
11func main() {
12    db, err := sql.Open("postgres", "user=zuolan password=zuolan dbname=test sslmode=disable")
13    checkErr(err)
14
15    //插入数据
16    stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) RETURNING uid")
17    checkErr(err)
18
19    res, err := stmt.Exec("张三", "研发部门", "2017-09-09")
20    checkErr(err)
21
22    // pg不支持这个函数,因为他没有类似MySQL的自增ID
23    // id, err := res.LastInsertId()
24    // checkErr(err)
25    // fmt.Println(id)
26
27    var lastInsertId int
28    err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "zuolan", "研发部门", "2017-09-09").Scan(&lastInsertId)
29    checkErr(err)
30    fmt.Println("最后插入id =", lastInsertId)
31
32
33    //更新数据
34    stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2")
35    checkErr(err)
36
37    res, err = stmt.Exec("zuolanupdate", 1)
38    checkErr(err)
39
40    affect, err := res.RowsAffected()
41    checkErr(err)
42
43    fmt.Println(affect)
44
45    //查询数据
46    rows, err := db.Query("SELECT * FROM userinfo")
47    checkErr(err)
48
49    for rows.Next() {
50        var uid int
51        var username string
52        var department string
53        var created string
54        err = rows.Scan(&uid, &username, &department, &created)
55        checkErr(err)
56        fmt.Println(uid)
57        fmt.Println(username)
58        fmt.Println(department)
59        fmt.Println(created)
60    }
61
62    //删除数据
63    stmt, err = db.Prepare("delete from userinfo where uid=$1")
64    checkErr(err)
65
66    res, err = stmt.Exec(1)
67    checkErr(err)
68
69    affect, err = res.RowsAffected()
70    checkErr(err)
71
72    fmt.Println(affect)
73
74    db.Close()
75
76}
77
78func checkErr(err error) {
79    if err != nil {
80        panic(err)
81    }
82}

从上面的代码我们可以看到,PostgreSQL是通过1,2这种方式来指定要传递的参数,而不是MySQL中的?,另外在sql.Open中的dsn信息的格式也与MySQL的驱动中的dsn格式不一样,所以在使用时请注意它们的差异。

还有pg不支持LastInsertId函数,因为PostgreSQL内部没有实现类似MySQL的自增ID返回,其他的代码几乎是一模一样。

NoSQL数据库操作

NoSQL(Not Only SQL),指的是非关系型的数据库。随着Web2.0的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。

而Go语言作为21世纪的C语言,对NOSQL的支持也是很好,目前流行的NOSQL主要有redis、mongoDB、Cassandra和Membase等。这些数据库都有高性能、高并发读写等特点,目前已经广泛应用于各种应用中。

1. Redis

redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。

目前应用redis最广泛的应该是新浪微博平台,其次还有Facebook收购的图片社交网站instagram。Go目前支持redis的驱动有如下:

推荐使用第一个,用法上和上面驱动没有太大区别,限于篇幅不再展开。

2. mongoDB

MongoDB是一个高性能,开源,无模式的文档型数据库,是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,采用的是类似json的bjson格式来存储数据,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

目前Go支持mongoDB最好的驱动就是mgo(http://labix.org/mgo),这个驱动目前最有可能成为官方的pkg。安装mgo的命令为

1go get gopkg.in/mgo.v2

8