[GO]go语言的mysql操作
1.连接驱动
go语言自带的database/sql包提供了一个保证SQL或类SQL数据库的泛用接口,go它并不提供具体的数据库驱动。使用database/sql
包时必须注入(至少)一个数据库驱动。我们一般使用一个第三方的包实现数据库的连接,比如mysql常用的是https://github.com/go-sql-driver/mysql这个驱动。
go get -u github.com/go-sql-driver/mysql
可以通过go get 指令下载对应的驱动。
使用open函数实现一个最简单的数据库驱动初始化,这个函数只会验证dsn的输入格式是否正确,不会实际创建数据库的连接。
open函数会返回一个db对象,下面使用了同名的db变量来接收,这个对象可以被多个go协程安全并发调用,因此,一般最后才会关闭db的连接。
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// DSN:Data Source Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_test"
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
defer db.Close() // 注意这行代码要写在上面err判断的下面
}
2.数据库的连接
我们使用ping命令来测试open传入的dsn数据是否正确,数据库有没有被正确的连接上。
// 定义一个全局对象db,他是一个数据库连接单例,用于接收open返回的db对象
var db *sql.DB
// 定义一个初始化数据库的函数
func initDB() (err error) {
// DSN:Data Source Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 给全局对象db传入open返回的db对象
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
// 尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
return err
}
return nil
}
func main() {
err := initDB() // 调用上面的初始化数据库的函数
if err != nil {
fmt.Printf("init db failed,err:%v\n", err)
return
}
}
db
是表示连接的数据库对象(结构体实例),它保存了连接数据库相关的所有信息。它内部维护着一个具有零到多个底层连接的连接池,它可以安全地被多个goroutine同时使用。
3.CRUD操作
准备工作,先建个表
CREATE DATABASE sql_test;
use sql_test;
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT '',
`age` INT(11) DEFAULT '0',
PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
3.1 查询
先在建的表上创建几个数据项。
定义一个结构体,用于存放返回的数据。
type user struct {
id int
age int
name string
}
进行单行的查询,使用QueryRow函数,传入查询的sql语句,以及查询条件。
func (db *DB) QueryRow(query string, args ...interface{}) *Row
实际查询id=2的代码如下
var db *sql.DB
type user struct {
id int
age int
name string
}
// 定义一个初始化数据库的函数
func initDB() (err error) {
// DSN:Data Source Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
// 尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
return err
}
return nil
}
// 查询单条数据示例
func queryRowDemo() {
sqlStr := "select id, name, age from user where id=?"
var u user
// 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
//func (db *DB) QueryRow(query string, args ...interface{}) *Row
err := db.QueryRow(sqlStr, 2).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
func main() {
err := initDB() // 调用输出化数据库的函数
if err != nil {
fmt.Printf("init db failed,err:%v\n", err)
return
}
queryRowDemo()
}
返回结果
进行多行的查询也类似,使用Query函数,传入查询的sql语句,以及查询条件。
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
查询id>0的多行查询示例如下:
// 查询多条数据示例
func queryMultiRowDemo() {
sqlStr := "select id, name, age from user where id > ?"
rows, err := db.Query(sqlStr, 0)
if err != nil {
fmt.Printf("query failed, err:%v\n", err)
return
}
// 非常重要:关闭rows释放持有的数据库链接
defer rows.Close()
// 循环读取结果集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
}
3.2 插入
插入、删除、更新都是通过Exec函数实现的:
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
首个参数为sql操作指令,后面的可变参数传入sql指令的参数,下面是一个插入示例。插入一个name=王五,age=38的数据项。
// 插入数据
func insertRowDemo() {
sqlStr := "insert into user(name, age) values (?,?)"
ret, err := db.Exec(sqlStr, "王五", 38)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theID, err := ret.LastInsertId() // 新插入数据的id
if err != nil {
fmt.Printf("get lastinsert ID failed, err:%v\n", err)
return
}
fmt.Printf("insert success, the id is %d.\n", theID)
}
3.3 删除
删除同样使用的Exec函数,下面给出一个删除示例,删除id=3的数据项。
// 删除数据
func deleteRowDemo() {
sqlStr := "delete from user where id = ?"
ret, err := db.Exec(sqlStr, 3)
if err != nil {
fmt.Printf("delete failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("delete success, affected rows:%d\n", n)
}
3.4 更新
更新同样使用Exec函数,下面给出一个更新示例,将id=3的数据项的age设置为39。
// 更新数据
func updateRowDemo() {
sqlStr := "update user set age=? where id = ?"
ret, err := db.Exec(sqlStr, 39, 3)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("update success, affected rows:%d\n", n)
}
4.go语言实现预处理
所谓预处理,就是将sql语句和sql执行的参数分成两次传递到sql服务器上,sql可以先解析sql语句,再在服务器上根据后续传入的参数进行相应占位符(mysql中占位符为?)位置的替换,合成实际的sql指令,将结果返回用户,而不必等待用户将sql语句和参数一起传入服务器统一解析。
预处理的优点:优化MySQL服务器重复执行SQL的情况,提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。也可以避免SQL注入问题。
go使用Prepare函数实现预处理,他会将传入的sql语句先传到sql服务器,等待参数的传入。返回一个连接对象,用于后续的命令。
func (db *DB) Prepare(query string) (*Stmt, error)
对查询进行预处理示例如下:
// 预处理查询示例
func prepareQueryDemo() {
sqlStr := "select id, name, age from user where id > ?"
//使用stmt接收返回的连接状态,需要关闭
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed, err:%v\n", err)
return
}
defer stmt.Close()
//rows用于接收传入参数后sql服务返回的结果
rows, err := stmt.Query(0)
if err != nil {
fmt.Printf("query failed, err:%v\n", err)
return
}
defer rows.Close()
// 循环读取结果集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
}
5.go实现事务管理
事务定义:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
在MySQL中只有使用了Innodb
数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。
事务具有四个特性ACID,原子性,一致性,持久性,隔离性。
go通常使用以下三个函数实现对事务的管理,分别对应着开始,提交和回滚事务:
func (db *DB) Begin() (*Tx, error)
func (tx *Tx) Commit() error
func (tx *Tx) Rollback() error
使用事务管理的示例如下,他可以保证ret1和ret2都被执行或都不执行:
// 事务操作示例
func transactionDemo() {
tx, err := db.Begin() // 开启事务
if err != nil {
if tx != nil {
tx.Rollback() // 回滚
}
fmt.Printf("begin trans failed, err:%v\n", err)
return
}
sqlStr1 := "Update user set age=30 where id=?"
ret1, err := tx.Exec(sqlStr1, 2)
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql1 failed, err:%v\n", err)
return
}
affRow1, err := ret1.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}
sqlStr2 := "Update user set age=40 where id=?"
ret2, err := tx.Exec(sqlStr2, 3)
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql2 failed, err:%v\n", err)
return
}
affRow2, err := ret2.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}
fmt.Println(affRow1, affRow2)
if affRow1 == 1 && affRow2 == 1 {
fmt.Println("事务提交啦...")
tx.Commit() // 提交事务
} else {
tx.Rollback()
fmt.Println("事务回滚啦...")
}
fmt.Println("exec trans success!")
}