• 《Go语言四十二章经》第三十九章 Mysql数据库
    • 39.1 database/sql包
    • 39.2 Mysql数据库操作

    《Go语言四十二章经》第三十九章 Mysql数据库

    作者:李骁

    39.1 database/sql包

    Go 提供了database/sql包用于对SQL数据库的访问,作为操作数据库的入口对象sql.DB,主要为我们提供了两个重要的功能:

    • sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操作.
    • sql.DB 为我们管理数据库连接池

    需要注意的是,sql.DB表示操作数据库的抽象访问接口, 而非一个数据库连接对象;它可以根据driver打开关闭数据库连接,管理连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。

    导入mysql数据库驱动

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

    通常来说,不应该直接使用驱动所提供的方法,而是应该使用 sql.DB,因此在导入 mysql 驱动时,这里使用了匿名导入的方式(在包路径前添加 _),当导入了一个数据库驱动后,此驱动会自行初始化并注册自己到Go的database/sql上下文中,因此我们就可以通过 database/sql 包提供的方法访问数据库了。

    39.2 Mysql数据库操作

    我们先建立表结构:

    1. CREATE TABLE t_article_cate (
    2. `cid` int(10) NOT NULL AUTO_INCREMENT,
    3. `cname` varchar(60) NOT NULL,
    4. `ename` varchar(100),
    5. `cateimg` varchar(255),
    6. `addtime` int(10) unsigned NOT NULL DEFAULT '0',
    7. `publishtime` int(10) unsigned NOT NULL DEFAULT '0',
    8. `scope` int(10) unsigned NOT NULL DEFAULT '10000',
    9. `status` tinyint(1) unsigned NOT NULL DEFAULT '0',
    10. PRIMARY KEY (`cid`),
    11. UNIQUE KEY catename (`cname`)
    12. ) ENGINE=InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

    下面代码使用预编译的方式,来进行增删改查的操作,并通过事务来批量提交一批数据。预编译语句(PreparedStatement)提供了诸多好处,可以实现自定义参数的查询,通常来说,比手动拼接字符串 SQL 语句高效,可以防止SQL注入攻击。

    1. package main
    2. import (
    3. "database/sql"
    4. "fmt"
    5. "strings"
    6. "time"
    7. _ "github.com/go-sql-driver/mysql"
    8. )
    9. type DbWorker struct {
    10. Dsn string
    11. Db *sql.DB
    12. }
    13. type Cate struct {
    14. cid int
    15. cname string
    16. addtime int
    17. scope int
    18. }
    19. // sql.NullInt64 sql.NullString
    20. // 因为Go是强类型语言,所以查询数据时先定义数据类型,
    21. // 但是查询数据库中的数据存在三种可能:
    22. // 存在值,存在零值,未赋值NULL 三种状态,因为可以将待查询的数据类型
    23. // 定义为sql.Nullxxx类型,
    24. // 可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值NULL状态。
    25. func main() {
    26. dbw := DbWorker{Dsn: "root:123456@tcp(localhost:3306)/mydb?charset=utf8mb4"}
    27. // 支持下面几种DSN写法,具体看mysql服务端配置,常见为第2种
    28. // user@unix(/path/to/socket)/dbname?charset=utf8
    29. // user:password@tcp(localhost:5555)/dbname?charset=utf8
    30. // user:password@/dbname
    31. // user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
    32. dbtemp, err := sql.Open("mysql", dbw.Dsn)
    33. dbw.Db = dbtemp
    34. if err != nil {
    35. panic(err)
    36. return
    37. }
    38. defer dbw.Db.Close()
    39. // 插入数据测试
    40. dbw.insertData()
    41. // 删除数据测试
    42. dbw.deleteData()
    43. // 修改数据测试
    44. dbw.editData()
    45. // 查询数据测试
    46. dbw.queryData()
    47. // 事务操作测试
    48. dbw.transaction()
    49. }

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

    插入数据:

    1. // 插入数据,sql预编译
    2. func (dbw *DbWorker) insertData() {
    3. stmt, _ := dbw.Db.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
    4. defer stmt.Close()
    5. ret, err := stmt.Exec("栏目1", time.Now().Unix(), 10)
    6. // 通过返回的ret可以进一步查询本次插入数据影响的行数
    7. // RowsAffected和最后插入的Id(如果数据库支持查询最后插入Id)
    8. if err != nil {
    9. fmt.Printf("insert data error: %v\n", err)
    10. return
    11. }
    12. if LastInsertId, err := ret.LastInsertId(); nil == err {
    13. fmt.Println("LastInsertId:", LastInsertId)
    14. }
    15. if RowsAffected, err := ret.RowsAffected(); nil == err {
    16. fmt.Println("RowsAffected:", RowsAffected)
    17. }
    18. }

    删除数据:

    1. // 删除数据,预编译
    2. func (dbw *DbWorker) deleteData() {
    3. stmt, err := dbw.Db.Prepare(`DELETE FROM t_article_cate WHERE cid=?`)
    4. ret, err := stmt.Exec(122)
    5. // 通过返回的ret可以进一步查询本次插入数据影响的行数RowsAffected和
    6. // 最后插入的Id(如果数据库支持查询最后插入Id).
    7. if err != nil {
    8. fmt.Printf("insert data error: %v\n", err)
    9. return
    10. }
    11. if RowsAffected, err := ret.RowsAffected(); nil == err {
    12. fmt.Println("RowsAffected:", RowsAffected)
    13. }
    14. }

    修改数据:

    1. // 修改数据,预编译
    2. func (dbw *DbWorker) editData() {
    3. stmt, err := dbw.Db.Prepare(`UPDATE t_article_cate SET scope=? WHERE cid=?`)
    4. ret, err := stmt.Exec(111, 123)
    5. // 通过返回的ret可以进一步查询本次插入数据影响的行数RowsAffected和
    6. // 最后插入的Id(如果数据库支持查询最后插入Id).
    7. if err != nil {
    8. fmt.Printf("insert data error: %v\n", err)
    9. return
    10. }
    11. if RowsAffected, err := ret.RowsAffected(); nil == err {
    12. fmt.Println("RowsAffected:", RowsAffected)
    13. }
    14. }

    查询数据:

    1. // 查询数据,预编译
    2. func (dbw *DbWorker) queryData() {
    3. // 如果方法包含Query,那么这个方法是用于查询并返回rows的。其他用Exec()
    4. // 另外一种写法
    5. // rows, err := db.Query("select id, name from users where id = ?", 1)
    6. stmt, _ := dbw.Db.Prepare(`SELECT cid, cname, addtime, scope From t_article_cate where status=?`)
    7. //err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) // 单行查询,直接处理
    8. defer stmt.Close()
    9. rows, err := stmt.Query(0)
    10. defer rows.Close()
    11. if err != nil {
    12. fmt.Printf("insert data error: %v\n", err)
    13. return
    14. }
    15. // 构造scanArgs、values两个slice,
    16. // scanArgs的每个值指向values相应值的地址
    17. columns, _ := rows.Columns()
    18. fmt.Println(columns)
    19. rowMaps := make([]map[string]string, 9)
    20. values := make([]sql.RawBytes, len(columns))
    21. scans := make([]interface{}, len(columns))
    22. for i := range values {
    23. scans[i] = &values[i]
    24. scans[i] = &values[i]
    25. }
    26. i := 0
    27. for rows.Next() {
    28. //将行数据保存到record字典
    29. err = rows.Scan(scans...)
    30. each := make(map[string]string, 4)
    31. // 由于是map引用,放在上层for时,rowMaps最终返回值是最后一条。
    32. for i, col := range values {
    33. each[columns[i]] = string(col)
    34. }
    35. // 切片追加数据,索引位置有意思。不这样写就不是希望的样子。
    36. rowMaps = append(rowMaps[:i], each)
    37. fmt.Println(each)
    38. i++
    39. }
    40. fmt.Println(rowMaps)
    41. for i, col := range rowMaps {
    42. fmt.Println(i, col)
    43. }
    44. err = rows.Err()
    45. if err != nil {
    46. fmt.Printf(err.Error())
    47. }
    48. }

    事务处理:
    db.Begin()开始事务,Commit() 或 Rollback()关闭事务。Tx从连接池中取出一个连接,在关闭之前都使用这个连接。Tx不能和DB层的BEGIN,COMMIT混合使用。

    1. func (dbw *DbWorker) transaction() {
    2. tx, err := dbw.Db.Begin()
    3. if err != nil {
    4. fmt.Printf("insert data error: %v\n", err)
    5. return
    6. }
    7. defer tx.Rollback()
    8. stmt, err := tx.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
    9. if err != nil {
    10. fmt.Printf("insert data error: %v\n", err)
    11. return
    12. }
    13. for i := 100; i < 110; i++ {
    14. cname := strings.Join([]string{"栏目-", string(i)}, "-")
    15. _, err = stmt.Exec(cname, time.Now().Unix(), i+20)
    16. if err != nil {
    17. fmt.Printf("insert data error: %v\n", err)
    18. return
    19. }
    20. }
    21. err = tx.Commit()
    22. if err != nil {
    23. fmt.Printf("insert data error: %v\n", err)
    24. return
    25. }
    26. stmt.Close()
    27. }