命令记不住

登陆

Mysql 编程入门

Mysql编程

1.MySQL环境

启动Msql Server容器

➜  ~ docker run --name MysqlGolang -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root1234 -d mysql:5.7

启动Msql Client容器

➜  ~ docker run -it --network host --rm mysql:5.7 mysql -h127.0.0.1 -P3306  -uroot -p

Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.32 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

创建一个数据库表结构:数据库s1,用户表userinfo,关联用户信息表userdetail。

mysql> CREATE DATABASE s1;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE TABLE `userinfo` (
   `uid` INT(10) NOT NULL AUTO_INCREMENT,
   `username` VARCHAR(64) NULL DEFAULT NULL,
   `departname` VARCHAR(64) NULL DEFAULT NULL,
   `created` DATE NULL DEFAULT NULL,
   PRIMARY KEY (`uid`)
   );
Query OK, 0 rows affected (0.07 sec)

mysql> CREATE TABLE `userdetail` (
     `uid` INT(10) NOT NULL DEFAULT '0',
     `intro` TEXT NULL,
     `profile` TEXT NULL,
     PRIMARY KEY (`uid`)
     );
Query OK, 0 rows affected (0.07 sec)

2.MySQL驱动

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

  • https://github.com/go-sql-driver/mysql 支持database/sql,全部采用go写。
  • https://github.com/ziutek/mymysql 支持database/sql,也支持自定义的接口,全部采用go写。
  • https://github.com/Philio/GoMySQL 不支持database/sql,自定义接口,全部采用go写

接下来我们主要以第一个驱动为例,也推荐大家采用它,主要理由:

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

g执行下面命令安装

go get github.com/go-sql-driver/mysql

3.连接数据库

3.1 首先导入包
import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)
  • database/sql,是golang的标准库之一,它提供了一系列接口方法,用于访问关系数据库。它并不会提供数据库特有的方法,那些特有的方法交给数据库驱动去实现。
  • 我们正在加载的驱动是匿名的,将其限定符别名为_。

当导入了一个数据库驱动后, 此驱动会自行初始化并注册自己到Golang的database/sql上下文中, 因此我们就可以通过 database/sql 包提供的方法访问数据库了。

3.2 建立连接

使用Open函数

func Open(driverName, dataSourceName string) (*DB, error)
/*
其中的两个参数:
drvierName,这个名字其实就是数据库驱动注册到 database/sql 时所使⽤用的名字.
"mysql"
dataSourceName,数据库连接信息,这个连接包含了数据库的用户名, 密码, 数据库主机以及需要
连接的数据库名等信息.
用户名:密码@协议(地址:端口)/数据库?参数=参数值
*/
db, err := sql.Open("mysql", "⽤用户名:密码@tcp(IP:端口)/数据库?charset=utf8mb4")

例如: db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True")

说明:

  • sql.Open 并不会立即建立一个数据库的网络连接, 也不会对数据库链接参数的合法性做检验, 它仅仅是初始化一个sql.DB对象. 当真正进行第一次数据库查询操作时, 此时才会真正建立网络连接;
  • sql.DB 表示操作数据库的抽象接口的对象,但不是所谓的数据库连接对象, sql.DB 对象只有当需要使用时才会创建连接,如果想立即验证连接,需要用 Ping() 方法;
  • sql.Open 返回的 sql.DB 对象是协程并发安全的.
  • sql.DB 的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。
package main

import (
	"database/sql"
	"fmt"
)

// DB 声明全局的DB对象
var DB *sql.DB

// initMySQL 初始化全局的DB对象
func initMySQL() error {
	dsn := "root:root1234@tcp(127.0.0.1:3306)/s2?charset=utf8mb4&parseTime=True"
	var err error
	DB, err = sql.Open("mysql", dsn) // 不会真正连接MySQL,也不会校验用户名密码
	if err != nil {
		fmt.Printf("open MySQL failed, err:%v\n", err)
		return err
	}

	// 通过Ping()去判断连通性
	if err := DB.Ping(); err != nil {
		fmt.Printf("connect to MySQL failed, err:%v\n", err)
		return err
	}
	fmt.Println("connect to MySQL success")
	return nil
}

4.DML操作:增删改

4.1增-有两种方法:
1.直接使用Exec函数添加

函数:

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

示例代码

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql" // 执行init()
)

func main() {
	err := initMySQL()
	if err != nil {
		fmt.Printf("initMySQL failed, err:%\n", err)
		return
	}

	defer DB.Close()
	// 连接成功,使用sql操作...
	// 增加记录1
	sqlStr := "INSERT INTO userinfo(username,departname,created) values(?,?,?)"
	result, err := DB.Exec(sqlStr, "王一博", "行政部", "2019-11-21")
	if err != nil {
		fmt.Printf("insert into userinfo failed, err:%v\n", err)
		return
	}
	fmt.Println(result.LastInsertId())
}
2.首先使用Prepare获得stmt,然后调用Exec添加。

建立连接后,通过操作DB对象的 Prepare() 方法,可以进行

函数:

func (db *DB) Prepare(query string) (*Stmt, error) {
	return db.PrepareContext(context.Background(), query)
}

示例代码

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql" // 执行init()
)

func main() {
	err := initMySQL()
	if err != nil {
		fmt.Printf("initMySQL failed, err:%\n", err)
		return
	}

	defer DB.Close()
	// 连接成功,使用sql操作...
	// 增加记录2
	stmt, err := DB.Prepare("INSERT INTO userinfo(username,departname,created) values(?,?,?)")
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	// 补充完整sql语句,并执行
	result, err := stmt.Exec("程潇", "市场部", "2020-05-01")
	lid, err := result.LastInsertId()
	if err != nil {
		fmt.Printf("get lastInsertID failed, err:%v\n", err)
		return
	}
	fmt.Println(lid)
}

预编译语句(Prepared Statement) 提供了诸多好处, 因此我们在开发中尽量使用它. 下面列出了使用预编译语句所提供的功能:

  • PreparedStatement 可以实现自定义参数的查询
  • PreparedStatement 通常比手动拼接字符串 SQL 语句高效
  • PreparedStatement 可以防止SQL注入攻击
3.处理结果:影响数据库的行数

获取影响数据库的行数,可以根据该数值判断是否插入或删除或修改成功。

count, err := result.RowsAffected()

获得 刚刚添加数据的自增ID

id, err := result.LastInsertId()

打开数据库,可以看到加入的数据。

4.2 改-更新操作

修改表内一行数据

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql" // 执行init()
)

func main() {
	err := initMySQL()
	if err != nil {
		fmt.Printf("initMySQL failed, err:%\n", err)
		return
	}

	defer DB.Close()
	// 连接成功,使用sql操作...
	// 修改一条数据,直接使用Exec()方法
	result, err := DB.Exec("UPDATE userinfo SET username = ?, departname = ? WHERE uid = ?", "wuqun", "cg", 2)
	if err != nil {
		fmt.Println("更新数据失败。。 ", err)
	}

	//处理sql操作后的结果
	lastInsertId, err := result.LastInsertId()
	rowsAffected, err := result.RowsAffected()
	fmt.Println("lastInsertId", lastInsertId)
	fmt.Println("影响的行数: ", rowsAffected)
}

关键语句

result, err := DB.Exec("UPDATE userinfo SET username = ?, departname = ? WHERE uid = ?", "王一博", "行政部", 2)
if err != nil {
	fmt.Println("更新数据失败。。 ", err)
}
// 处理理sql操作后的结果
lastInsertId, err := result.LastInsertId()
rowsAffected, err := result.RowsAffected()
fmt.Println("lastInsertId", lastInsertId)
fmt.Println("影响的行数: ", rowsAffected)

4.3 删-DELETE操作
DELETE

delete

5.DQL操作:查询

1. 查询一条

查询单条数据, QueryRow 函数

func (db *DB) QueryRow(query string, args ...interface{}) *Row

示例代码

var username, departname, created string
row := db.QueryRow("SELECT username,departname,created FROM userinfo WHERE
uid=?", 3)
err := row.Scan(&username, &departname, &created)
//也可以简写:
err := db.QueryRow("SELECT username,departname,created FROM userinfo WHERE
uid=?", 3).Scan(&username, &departname, &created)

扫描并复制当前行中每一列的值,但是要求行必须与行中的列数相同

func (rs *Rows) Scan(dest ...interface{}) error
  • rows.Scan 参数的顺序很重要, 需要和查询的结果的column对应. 例如 “SELECT * From user where age >=20 AND age < 18” 查询的行的 column 顺序是 “id, name, age” 和插入操作顺序相同, 因此 rows.Scan 也需要按照此顺序 rows.Scan(&id, &name, &age), 不然会造成数据读取的错位.
  • 因为golang是强类型语言,所以查询数据时先定义数据类型,但是查询数据库中的数据存在三种可能:存在值,存在零值,未赋值NULL 三种状态, 因为可以将待查询的数据类型定义为 sql.Nullxxx类型,可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值NULL状态.
2. 查询多条数据,并遍历

Query 获取数据, for xxx.Next() 遍历数据:
首先使用Query()方法进行查询,如果查询无误,返回Rows,就是所有行的信息,类似结果集。

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

我们可以通过Next()方法判断是否存在下一条数据,如果有,可以使用之前的Scan()⽅方法读取一行,然后继续判断,继续获取。这个过程的本质就是迭代,所以通常要配合循环使用

func (rs *Rows) Next() bool

每次 db.Query 操作后,都建议调用 rows.Close() 。

因为 db.Query() 会从数据库连接池中获取一个连接, 这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但如果提前退出循环, rows不会关闭,连接不会回到连接池中,连接也不会关闭,则此连接会一直被占用。

因此通常我们使⽤用 defer rows.Close() 来确保数据库连接可以正确放回到连接池中。

示例

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql" // 执行init()
)

func main() {
	err := initMySQL()
	if err != nil {
		fmt.Printf("initMySQL failed, err:%\n", err)
		return
	}

	defer DB.Close()
	// 连接成功,使用sql操作...
	// 查询单条
	var username, departname, created string
	err = DB.QueryRow("SELECT username,departname,created FROM userinfo WHERE uid=?", 2).
		Scan(&username, &departname, &created) // 传指针
	if err != nil {
		fmt.Printf("QueryRow failed, err:%v\n", err)
		return
	}
	fmt.Println(username, departname, created)

	// 查询多条
	rows, err := DB.Query("SELECT uid, username, departname, created FROM userinfo")
	if err != nil {
		fmt.Println("查询有误。。")
		return
	}

	// fmt.Println(rows.Columns()) //[uid username departname created]
	defer rows.Close()

	type User struct {
		UID        int
		UserName   string
		DepartName string
		Created    string
	}
	// 创建slice,存入struct,
	var data []*User
	// step4:操作结果集获取数据
	for rows.Next() {
		var (
			uid        int
			username   string
			departname string
			created    string
		)

		if err := rows.Scan(&uid, &username, &departname, &created); err != nil {
			fmt.Println("获取失败。。")
		}

		//每读取一行,创建一个user对象,存入datas2中
		user := &User{uid, username, departname, created}
		data = append(data, user)
	}

	for _, v := range data {
		fmt.Printf("%#v\n", v)
	}
}
两个关键点
  • 优先使用预编译执行SQL语句
  • 查询的时候
    • QueryRow().Scan()
    • Query()返回的Rows,遍历完遇到EOF才会释放连接

6.事务

事务操作是通过三个方法实现:

  • Begin():开启事务
  • Commit():提交事务(执行sql)
  • Rollback():回滚

在创建一个表,并插入两条数据

CREATE TABLE `account` (
`id` int(4) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`money` float(8,2) DEFAULT NULL,
PRIMARY KEY (`id`)
)
INSERT INTO `account` VALUES ('1', 'ycy', '3000.00');
INSERT INTO `account` VALUES ('2', 'wyb', '1000.00');

现在我们想让杨超越转账给王一博2000元。
如果转账成功,修改杨超越和王一博的金额。否则金额保持不变。

新建go文件(demo08_mysql_transaction.go),示例代码:

package main

import "fmt"

func main() {
	err := initMySQL()
	if err != nil {
		fmt.Printf("initMySQL failed, err:%\n", err)
		return
	}
	defer DB.Close()
	/*
	   事务4大特性: ACID
	   原子性:
	   一致性:
	   隔离性:
	   永久性:
	*/
	// 杨超越 --> 王一博,2000元
	tx, _ := DB.Begin()
	// 提供一组sql操作
	var aff1, aff2 int64 = 0, 0
	result1, _ := tx.Exec("UPDATE account SET money=3000 WHERE id=?", 1)
	result2, _ := tx.Exec("UPDATE account SET money=2000 WHERE id=?", 2)
	if result1 != nil {
		aff1, _ = result1.RowsAffected()
	}
	if result2 != nil {
		aff2, _ = result2.RowsAffected()
	}
	fmt.Println(aff1)
	fmt.Println(aff2)
	if aff1 == 1 && aff2 == 1 {
		//提交事务
		tx.Commit()
		fmt.Println("操作成功。。 ")
	} else {
		//回滚
		tx.Rollback()
		fmt.Println("操作失败... 回滚... ")
	}
}

由于事务是一个一直连接的状态,所以Tx对象必须绑定和控制单个连接。一个Tx会在整个生命周期中保存一个连接,然后在调用 commit() 或 Rollback() 的时候释放掉。在调用这几个函数的时候必须十分小心,否则连接会一直被占用直到被垃圾回收。

7.sqlx扩展包

sqlx扩展包比原生SQL好用

sqlx包是作为 database/sql 包的一个额外扩展包,在原有的 database/sql 加了很多扩展,如直接将查询的数据转为结构体,大大简化了代码书写,当然database/sql包中的方法同样起作用。

安装:

go get "github.com/jmoiron/sqlx"

连接数据库:

var DB *sqlx.DB
DB, err = sqlx.Open("mysql","username:password@tcp(ip:port)/database?
charset=utf8")

处理类型(Handle Types)

  • sqlx设计和database/sql使用方法是一样的。包含有4种主要的handle types:
  • sqlx.DB - 和sql.DB相似,表示数据库。
  • sqlx.Tx - 和sql.Tx相似,表示事物。
  • sqlx.Stmt - 和sql.Stmt相似,表示prepared statement。
  • sqlx.NamedStmt - 表示prepared statement(支持named parameters)

所有的handler types都提供了对database/sql的兼容,意味着当你调用sqlx.DB.Query时,可以直接替换为sql.DB.Query.这就使得sqlx可以很容易的加入到已有的数据库项目中。
此外, sqlx还有两个cursor类型:

  • sqlx.Rows - 和sql.Rows类似, Queryx返回。
  • sqlx.Row - 和sql.Row类似, QueryRowx返回

相比database/sql方法还多了新语法,也就是实现将获取的数据直接转换结构体实现。

  • Get(dest interface{}, …) error
  • Select(dest interface{}, …) error
Get 和 Select(非常常用)

Get和Select是一个非常省时的扩展,可直接将结果赋值给结构体,其内部封装了StructScan进行转化。
Get用于获取单个结果然后Scan, Select用来获取结果切片。

新建go文件(demo09_kuozhan.go),示例代码:

package main

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var DB *sqlx.DB

// initMySQL 初始化全局的DB对象
func initMySQL() error {
	dsn := "root:root1234@tcp(127.0.0.1:3306)/s2?charset=utf8mb4&parseTime=True"
	var err error
	DB, err = sqlx.Connect("mysql", dsn)
	if err != nil {
		fmt.Printf("connect to MySQL failed, err:%v\n", err)
		return err
	}
	fmt.Println("connect to MySQL success")
	return nil
}
func main() {
	if err := initMySQL(); err != nil {
		fmt.Println(err.Error())
		return
	}
	type User struct {
		UID        int    `db:"uid"`
		Username   string `db:"username"`
		Departname string `db:"departname"`
		Created    string `db:"created"`
	}
	// 使⽤用Get查询单条记录
	var user User
	sqlStr := "SELECT uid,username,departname,created FROM userinfo where uid =?"
	if err := DB.Get(
		&user, sqlStr, 1); err != nil {
		fmt.Println("GET error :", err)
		return
	}
	fmt.Printf("this is GET res:%v\n", user)
	// 查询多条记录
	var users []User
	if err := DB.Select(&users, "SELECT uid,username,departname,created FROM userinfo"); err != nil {
		fmt.Println("Select error", err)
		return
	}
	fmt.Printf("this is Select res:%v\n", users)
}

运行结果:

this is GET res:{1 王一博 行政部 2019-11-21T00:00:00Z}
this is Select res:[{1 王一博 行政部 2019-11-21T00:00:00Z} {2 杨超越 技术部 2020-
05-17T00:00:00Z}]

8.sqlx.In

sqlx.In

实现部分:

// QueryByIDs 根据给定ID查询
func QueryByIDs(ids []int) (users []User, err error) {
	// 动态填充id
	// sqlx.In 相当于sqlx帮我们拼接SQL语句的占位符
	// SELECT username, departname from userinfo where id in (1,3)
	query, args, err := sqlx.In("SELECT uid,username,departname,created FROM userinfo WHERE uid IN (?)", ids)
	if err != nil {
		return
	}
	// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它
	query = DB.Rebind(query)
	err = DB.Select(&users, query, args...)
	return
}

动态传入id自动进行占位符拼接查询数据表:

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var DB *sqlx.DB

// initMySQL 初始化全局的DB对象
func initMySQL() error {
	dsn := "root:root1234@tcp(127.0.0.1:3306)/s2?charset=utf8mb4&parseTime=True"
	var err error
	DB, err = sqlx.Connect("mysql", dsn)
	if err != nil {
		fmt.Printf("connect to MySQL failed, err:%v\n", err)
		return err
	}
	fmt.Println("connect to MySQL success")
	return nil
}

// QueryByIDs 根据给定ID查询
func QueryByIDs(ids []int) (users []User, err error) {
	// 动态填充id
	// sqlx.In 相当于sqlx帮我们拼接SQL语句的占位符
	// SELECT username, departname from userinfo where id in (1,3)
	query, args, err := sqlx.In("SELECT uid,username,departname,created FROM userinfo WHERE uid IN (?)", ids)
	if err != nil {
		return
	}
	// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它
	query = DB.Rebind(query)

	err = DB.Select(&users, query, args...)
	return
}

type User struct {
	UID        int    `db:"uid"`
	Username   string `db:"username"`
	Departname string `db:"departname"`
	Created    string `db:"created"`
}

func main() {
	if err := initMySQL(); err != nil {
		fmt.Println(err.Error())
		return
	}

	// 根据指定ID列表查询数据
	ids := []int{2, 3}
	res, err := QueryByIDs(ids)
	if err != nil {
		fmt.Printf("QueryByIDs failed, err:%v\n", err)
		return
	}
	fmt.Println(res)
}

9.go-sql-driver包SQL操作

建立表结构:

CREATE TABLE t_article_cate(
`cid` int(10) NOT NULL AUTO_INCREMENT,
`cname` varchar(60) NOT NULL,
`ename` varchar(100),
`cateimg` varchar(255),
`addtime` int(10) unsigned NOT NULL DEFAULT'0',
`publishtime` int(10) unsigned NOT NULL DEFAULT'0',
`scope` int(10) unsigned NOT NULL DEFAULT'1000',
`status` tinyint(1) unsigned NOT NULL DEFAULT'0',
PRIMARY KEY(`cid`),
UNIQUE KEY catename(`cname`)
)ENGINE = InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

一般查询数据时先定义数据类型,但是查询数据库中的数据时有三种状态:存在值、存在零值、未赋值NULL,因此可以将待查询的数据类型定义为sql.Nullxxx类型,可以通过Valid值来判断查询到的值是赋值状态还是未赋值NULL状态,例如sql.NullInt64、sql.NullString

package main

import (
    "database/sql"
    "fmt"
    "strings"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

type DbWorker struct {
    Dsn string
    Db  *sql.DB
}

type Cate struct {
    cid     int
    cname   string
    addtime int
    scope   int
}

func main() {
    // 注意修改mydb为自己的数据库名
    dbw := DbWorker{Dsn: "root:123456@tcp(localhost:3306)/mydb?charset=utf8mb4"}
    // 支持下面几种DSN写法,具体看mysql服务端配置,常见为第2种
    // user@unix(/path/to/socket)/dbname?charset=utf8
    // user:password@tcp(localhost:5555)/dbname?charset=utf8
    // user:password@/dbname
    // user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

    dbtemp, err := sql.Open("mysql", dbw.Dsn)
    dbw.Db = dbtemp

    if err != nil {
        panic(err)
        return
    }
    defer dbw.Db.Close()

    // 插入数据测试
    dbw.insertData()

    // 删除数据测试
    dbw.deleteData()

    // 修改数据测试
    dbw.editData()

    // 查询数据测试
    dbw.queryData()

    // 事务操作测试
    dbw.transaction()
}

// 插入数据,sql预编译
func (dbw *DbWorker) insertData() {
    stmt, _ := dbw.Db.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
    defer stmt.Close()

    ret, err := stmt.Exec("栏目1", time.Now().Unix(), 10)
    // 通过返回的ret可以进一步查询本次插入数据影响的行数
    // RowsAffected和最后插入的Id(如果数据库支持查询最后插入Id)
    if err != nil {
        fmt.Printf("插入数据出错: %v\n", err)
        return
    }
    if LastInsertId, err := ret.LastInsertId(); nil == err {
        fmt.Println("最后插入记录的ID:", LastInsertId)
    }
    if RowsAffected, err := ret.RowsAffected(); nil == err {
        fmt.Println("插入有效的行数:", RowsAffected)
    }
}

// 删除数据,预编译
func (dbw *DbWorker) deleteData() {
    stmt, err := dbw.Db.Prepare(`DELETE FROM t_article_cate WHERE cid=?`)
    ret, err := stmt.Exec(122)
    // 通过返回的ret可以进一步查询本次删除数据影响的行数RowsAffected
    if err != nil {
        fmt.Printf("删除数据出错: %v\n", err)
        return
    }
    if RowsAffected, err := ret.RowsAffected(); nil == err {
        fmt.Println("删除有效的行数::", RowsAffected)
    }
}

// 修改数据,预编译
func (dbw *DbWorker) editData() {
    stmt, err := dbw.Db.Prepare(`UPDATE t_article_cate SET scope=? WHERE cid=?`)
    ret, err := stmt.Exec(111, 123)
    // 通过返回的ret可以进一步查询本次修改数据影响的行数RowsAffected
    if err != nil {
        fmt.Printf("修改数据出错: %v\n", err)
        return
    }
    if RowsAffected, err := ret.RowsAffected(); nil == err {
        fmt.Println("修改有效的行数::", RowsAffected)
    }
}

// 查询数据,预编译
func (dbw *DbWorker) queryData() {
    // 如果方法包含Query,那么这个方法是用于查询并返回rows的。其他用Exec()
    // 另外一种写法
    // rows, err := db.Query("select id, name from users where id = ?", 1)
    stmt, _ := dbw.Db.Prepare(`SELECT cid, cname, addtime, scope From t_article_cate where status=?`)
    // err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
    // 单行查询,直接处理
    defer stmt.Close()

    rows, err := stmt.Query(0)
    defer rows.Close()
    if err != nil {
        fmt.Printf("查询数据出错: %v\n", err)
        return
    }

    columns, _ := rows.Columns() // 得到字段名
    fmt.Println("字段名:", columns)

    // 下面两种取数据方式都比较原生,实际中一般都选择ORM框架
    // 1.数据保存到结构体
    cate := Cate{}
    for rows.Next() {
        //将行数据保存到结构体
        err = rows.Scan(&cate.cid, &cate.cname, &cate.addtime, &cate.scope)
        fmt.Println(cate)
    }
    /*
        // 2.通过slice取数据
        rowMaps := make([]map[string]string, 4) // 数据key:value的slice
        values := make([]sql.RawBytes, len(columns))
        scans := make([]interface{}, len(columns))
        for i := range values {
            scans[i] = &values[i]
        }
        i := 0
        for rows.Next() {
            //将行数据保存到字典
            err = rows.Scan(scans...)          // 保存当前数据行row的值到slice
            each := make(map[string]string, 4) // 定义map(key:value)

            for i, col := range values {
                each[columns[i]] = string(col) // 写k/v 数据
            }

            // rowMaps切片追加数据,索引位置有意思。不这样写就不是希望的样子。
            rowMaps = append(rowMaps[:i], each)
            fmt.Println(each)
            i++
        }

        for i, col := range rowMaps {
            fmt.Println(i, col)
        }
    */
    err = rows.Err()
    if err != nil {
        fmt.Printf(err.Error())
    }

    err = rows.Err()
    if err != nil {
        fmt.Printf(err.Error())
    }
}

// 事务处理,预编译
func (dbw *DbWorker) transaction() {
    tx, err := dbw.Db.Begin()
    if err != nil {
        fmt.Printf("事务处理数据出错: %v\n", err)
        return
    }
    defer tx.Rollback()
    stmt, err := tx.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
    if err != nil {
        fmt.Printf("事务处理数据出错: %v\n", err)
        return
    }

    for i := 100; i < 110; i++ {
        cname := strings.Join([]string{"栏目-", string(i)}, "-")
        _, err = stmt.Exec(cname, time.Now().Unix(), i+20)
        if err != nil {
            fmt.Printf("事务处理插入数据出错: %v\n", err)
            return
        }
    }
    err = tx.Commit()
    if err != nil {
        fmt.Printf("事务处理提交出错: %v\n", err)
        return
    }
    stmt.Close()
    fmt.Println("事务处理成功完成\n")
}

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

db.Begin()开始事务,Commit()或Robllback()关闭事务

推荐阅读