Go语言MySQL实践|青训营

101 阅读6分钟

通过GO页面作为客户端访问数据库

  • 因为GO语言没有提供任何官方数据库驱动,所以需要安装第三方函数库。
  • 由于在github上安装,所以需要安装git软件,必要时配置SSH插件

下载依赖

go get -u github.com/go-sql-driver/mysql
//-u: 这是go get命令的一个标志,表示更新已有的模块或下载已有模块的新版本。

使用MySQL驱动

Open打开一个driverName指定的数据库驱动,dataSourceName指定数据源具有一定的格式连接信息字符串。

//sql.Open()中的数据库连接串格式为:`"用户名:密码@tcp(IP:端口)/数据库?charset=utf8"`
func Open(driverName, dataSourceName string) (*DB, error)

示例代码

func main() {
	//sql.Open()中的数据库连接串格式为:"用户名:密码@tcp(IP:端口)/数据库?charset=utf8"
	dsn := "root:521109#Xsq@tcp(127.0.0.1:3306)/mysql?charset=utf8"
	//open打开一个drivername指定的数据库,datasourcename指定数据源
	//不会校验用户和密码是否正确,只会对dsn的格式进行检测
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		fmt.Println("打开数据库失败,err:", err)
		return
	}
	//尝试连接数据库,Ping方法可检查数据源名称是否合法,账号密码是否正确。
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库连接失败,请检查用户密码是否正确,err:", err)
		return
	}
	fmt.Println("连接数据库成功")
}

初始化连接

DB(数据库对象)

sql.DB类型代表了数据库,其它语言操作数据库的时候,需要创建一个连接,对于Go而言则是需要创建一个数据库类型, 它不是数据库连接,Go中的连接来自内部实现的连接池,连接的建立是惰性的,连接将会在操作的时候,由连接池创建并维护。

数据库CRUD

DB的类型为:*sql.DB,有了DB之后我们就可以执行CRUD操作。Go将数据库操作分为两类:QueryExec。两者的区别在于前者会返回结果,而后者不会。

  • Query表示查询,它会从数据库获取查询结果(一系列行,可能为空)。
  • Exec表示执行语句,它不会返回行。

此外还有两种常见的数据库操作模式:

  • QueryRow表示只返回一行的查询,作为Query的一个常见特例。
  • Prepare表示准备一个需要多次使用的语句,供后续执行用。

建库建表语句

CREATE DATABASE IF NOT EXISTS go_test;
use go_test;
CREATE TABLE `user` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
    `name` VARCHAR(20) DEFAULT '' COMMENT '用户昵称',
    `age` INT(11) DEFAULT '0' COMMENT '用户年龄',
    PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

DB.exec方法说明

插入、更新和删除操作都使用Exec()方法。Exec执行一次命令(包括查询、删除、更新、插入等),返回的Result是对已执行的SQL命令的总结。参数args表示query中的占位参数。

  • 插入数据后可以通过 LastInsertId() 方法获取插入数据的主键 id
  • 通过 RowsAffected 可以获取受影响的行数
  • 通过 Exec() 方法插入数据,返回的结果是 sql.Result 类型
func (db *DB) Exec(query string, args ...interface{}) (Result, error)

需要注意的是,不同的数据库,使用的占位符不同,mysql采用?作为占位符,其余数据库占位符说明如下:

image.png

更新数据库

func upDateRow(updateuser user) {
	sqlstr := "update user set age=?,name=? where id = ?"
	ret, err := db.Exec(sqlstr, updateuser.age, updateuser.name, updateuser.id)
	if err != nil {
		fmt.Printf("更新失败,err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected()
	if err != nil {
		fmt.Printf("获取影响行数失败 err:%v\n", err)
		return
	}
	fmt.Printf("更新成功,影响行数为 n:%d\n", n)
}

插入数据

func insertData(data user) {
	sqlstr := "INSERT INTO `user`(id,name,age) VALUES (?,?,?)"
	ret, err := db.Exec(sqlstr, data.id, data.name, data.age)
	if err != nil {
		fmt.Printf("插入数据失败,err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected()
	if err != nil {
		fmt.Printf("获取影响行数失败:%v\n", err)
		return
	}
	fmt.Printf("插入成功,影响行数为 n:%d\n", n)
}

删除数据

func deleteData(dedata user) {
	sqlstr := "DELETE FROM user WHERE id = ? AND name =?"
	ret, err := db.Exec(sqlstr, dedata.id, dedata.name)
	if err != nil {
		fmt.Printf("删除数据失败,err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected()
	if err != nil {
		fmt.Printf("获取影响行数失败:%v\n", err)
		return
	}
	fmt.Printf("删除成功,影响行数为 n:%d\n", n)
}

查询数据

单行查询

单行查询db.QueryRow()执行一次查询,并期望返回最多一行结果(即Row)。语法如下:

func (db *DB) QueryRow(query string, args ...interface{}) *Row
func queryRow(qudata user) {
	sqlstr := "SELECT id,name,age FROM user WHERE id=?"
	row := db.QueryRow(sqlstr, qudata.id)
	var u user
	err := row.Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("获取数据错误, err:%v\n", err)
		return
	}
	fmt.Printf("查询数据成功%#v", u)
}

多行查询

多行查询db.Query()执行一次查询,返回多行结果(即sql.Rows类型的结果集), 迭代后者使用Next()方法,然后使用Scan()方法给对应类型变量赋值,以便取出结果,最后再把结果集关闭(释放连接)语法如下:

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
func queryRows() {
	sqlstr := "SELECT id,name,age FROM user WHERE id>? OR id<?"
	rows, err := db.Query(sqlstr, 0, 20)
	if err != nil {
		fmt.Println("查询失败,err", err)
		return
	}
	defer rows.Close()
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Println("scan失败,err", err)
			return
		}
		fmt.Printf("查询数据成功%#v\n", u)
	}
}

MySQL预处理

什么是预处理

普通SQL语句得执行过程

  • 客户端对SQL语句进行占位符替换得到完整的SQL语句。
  • 客户端发送完整SQL语句到MySQL服务端
  • MySQL服务端执行完整的SQL语句并将结果返回给客户端

预处理执行过程:

  1. 把SQL语句分成两部分,命令部分与数据部分。
  2. 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
  3. 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
  4. MySQL服务端执行完整的SQL语句并将结果返回给客户端

为什么要预处理

  • 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
  • 避免SQL注入问题。

MySQL预处理

Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。

func (db *DB) Prepare(query string) (*Stmt, error)
func prepareQueryRow() {
	sqlstr := "SELECT id,name,age FROM user WHERE id > ?"
	stmt, err := db.Prepare(sqlstr)
	if err != nil {
		fmt.Println("预处理失败,err", err)
		return
	}
	defer stmt.Close()
	rows, err := stmt.Query(0)
	if err != nil {
		fmt.Println("查询失败,err", err)
		return
	}
	defer rows.Close()
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Println("scan失败,err", err)
			return
		}
		fmt.Printf("查询数据成功%#v\n", u)
	}
}

增删改预处理

func prepareInsertData() {
	sqlstr := "INSERT INTO user(id,name,age) VALUES(?,?,?)"
	stmt, err := db.Prepare(sqlstr)
	if err != nil {
		fmt.Printf("预处理失败, err:%v\n", err)
		return
	}
	defer stmt.Close()
	for i := 10; i < 15; i++ {
		id := 20 + i
		name := fmt.Sprintf("name%02d", i)
		stmt.Exec(id, name, i)
	}
	fmt.Println("批量处理成功")
}

MySQL事务

什么是事务

事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元),同时这个完整的业务需要执行多次的DML(insert、update、delete)语句共同联合完成。A转账给B,这里面就需要执行两次update操作。 在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。

事务得ACID

通常事务必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

image.png

事务相关方法

//开始事务
func (db *DB) Begin() (*Tx, error)
//提交事务
func (tx *Tx) Commit() error
//回滚事务
 func (tx *Tx) Rollback() error
func transDemo() {
	tx, err := db.Begin()
	if err != nil {
		if tx != nil {
			tx.Rollback()
		}
		fmt.Println("事务开启失败,err", err)
		return
	}
	sqlstr := "UPDATE user SET age=age+? WHERE id=?"
	_, err = tx.Exec(sqlstr, 2, 31)
	if err != nil {
		tx.Rollback()
		fmt.Println("sql1执行失败,err", err)
		return
	}
	sqlstr = "UPDATE user SET age=age-? WHERE id=?"
	_, err = tx.Exec(sqlstr, 2, 32)
	if err != nil {
		tx.Rollback()
		fmt.Println("sql1执行失败,err", err)
		return
	}
	err = tx.Commit()
	if err != nil {
		tx.Rollback()
		fmt.Println("事务提交失败,err", err)
		return
	}
	fmt.Println("数据更新成功")
}