Go操作MySQL
Go操作MySql
连接
go语言中的database/sql
包提供了Sql数据库的泛用接口,但是不提供具体的驱动。使用这个包时必须注入数据库驱动。
下载依赖
go get -u github.com/go-sql-driver/mysql
使用MySql驱动
func Open(driverName, dataSourceName string) (*DB, error)
打开一个指定driverName的数据库,dataSourceName指定数据源
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func Open() {
// DSN:data source name
dsn := "root:123@tcp(127.0.0.1:3306)/t1231est"
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Printf("打开失败,db:%#v", db)
panic(err)
}
defer db.Close() // 写在error判断的下面
fmt.Printf("成功打开数据库,db:%#v", db)
}
初始化连接
Open函数只有检查参数格式的作用,并不创建与数据库的连接,如需检查数据源是否有效应该使用Ping方法。
Open得到的DB对象可以被多个goroutine并发使用,并且维护一个空闲的连接池。所以Open函数应该只被调用一次,很少关闭DB对象。
// 定义一个全局变量
var db *sql.DB // 表示连接的数据库对象, 内部维护着一个连接池,可以安全的被多个goroutine使用
func initDB() (err error) {
dsn := "root:123@tcp(127.0.0.1:3306)/tes22t"
db, err = sql.Open("mysql", dsn)
if err != nil {
fmt.Printf("打开失败,db:%#v", db)
return err
}
// 尝试与数据库建立连接
err = db.Ping()
if err != nil {
return err
}
return nil
}
SetMaxOpenConns
func (db *DB) SetMaxOpenConns(n int)
设置与数据库建立连接的最大数目。n≤0时连接数无限制,0<n<最大闲置连接数时,最大闲置连接数会减小到最大连接数。
SetMaxIdleConns
func (db *DB) SetMaxIdleConns(n int)
设置连接池中的最大闲置连接数。n ≤ 0时不会保留闲置连接,n > 最大开启连接数时,最大闲置连接数减小到最大开启连接数。
CRUD
创建表
选定或新建一个数据库,使用以下SQL创建一张表
CREATE TABLE `user` (
`id` int(4) NOT NULL,
`name` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
查询
首先封装结构体代表表中的每一行记录
// 创建结构体封装数据库查询对象
type user struct {
id int
name string
}
单行查询
通过db.QueryRow()
来查询单行数据,期望返回最多一行结果。
返回值的Scan
方法被调用时才会返回被延时的错误。
func (db *DB) QueryRow(query string, args ...interface{}) *Row
示例:
func queryRowDemo() {
sqlStr := "select id, name from user where id = ?"
var u user
// 确保QueryRow之后调用Scan方法,否则持有的数据库连接不会被释放
err := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.name)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s\n", u.id, u.name)
}
多行查询
db.Query()执行一次查询返回多行数据
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
示例:
func queryMultiRowDemo() {
sqlStr := "select id, name from user where id > 0"
rows, err := db.Query(sqlStr)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
// 非常重要,关闭rows释放所有连接数据
defer rows.Close()
// 循环读取结果集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name)
if err != nil {
fmt.Printf("Scan failed ,err: %v\n", err)
return
}
fmt.Printf("id:%d name:%s\n", u.id, u.name)
}
}
插入
插入、更新和删除操作都使用Exec
方法。
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
Result是对已执行的SQL命令的总结, 参数args表示query中的占位参数。
示例:
func insertRowDemo() {
sqlstr := "insert into user (id, name) values (?, ?)"
ret, err := db.Exec(sqlstr, 223, "王五")
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theID, err := ret.LastInsertId()
if err != nil {
fmt.Printf("get lastinsert ID failed, err:%v\n", err)
return
}
fmt.Printf("insert success, the id is %d.\n", theID)
}
更新
func updateRowDemo() {
sqlStr := "update user set name = ? where id = ?"
ret, err := db.Exec(sqlStr, "张s", 222)
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)
}
删除
func deleteRowDemo() {
sqlstr := "delete from user where id = ?"
ret, err := db.Exec(sqlstr, 2)
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)
}
预处理
概念
不使用预处理时完整的SQL语句会发送给客户端,会有SQL注入的风险。使用预处理可以将SQL与数据拆分,先将要执行的SQL“模板”发送给数据库预处理,之后再将占位符参数发送给数据库,这样就可以防止SQL注入。
普通SQL语句执行过程:
- 客户端对SQL语句进行占位符替换得到完整的SQL语句。
- 客户端发送完整SQL语句到MySQL服务端
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
预处理执行过程:
- 把SQL语句分成两部分,命令部分与数据部分。
- 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
- 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
优点
- 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
- 避免SQL注入问题。
Go实现MySql预处理语句
database/sql
中使用下面的Prepare
方法来实现预处理操作。
func (db *DB) Prepare(query string) (*Stmt, error)
返回值可以同时执行多个查询和命令。
查询示例:
func prepareQueryDemo() {
sqlstr := "select id, name from user where id > ?"
s, err := db.Prepare(sqlstr)
if err != nil {
fmt.Printf("prepare failed, err:%v", err)
return
}
defer s.Close()
rows, err := s.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)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s\n", u.id, u.name)
}
}
插入、删除、更新示例:
func prepareInsertDemo() {
sqlStr := "insert into user (id, name) values (?, ?)"
s, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed err:%v", err)
return
}
defer s.Close()
_, err = s.Exec(123, "大王子")
if err != nil {
fmt.Printf("insert failed err:%v", err)
return
}
_, err = s.Exec(124, "小公主")
if err != nil {
fmt.Printf("insert failed err:%v", err)
return
}
fmt.Printf("insert succeed")
}
Go实现MySql事务
开启事务
func (db *DB) Begin() (*Tx, error)
提交事务
func (tx *Tx) Commit() error
回滚事务
func (tx *Tx) Rollback() error
示例
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 name = ? where id = ?"
ret1, err := tx.Exec(sqlStr1, "大公主", 124)
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 name='王大锤' 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!")
}