Go语言操作mysql指南

Posted 陈谋人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言操作mysql指南相关的知识,希望对你有一定的参考价值。

前言

今天一来到公司,就像往常的那样打开微信,然后看到有个群友说到,sqlx如何将mysql查询出来的字段映射在结构体上的两个字段,我一开始也心想着,查询出来直接赋值?利用mysql查询语句的别名映射,随后我打开代码编辑器实践地起来,其中一开始我就是利用sqlx的Get(dest interface{}, ...) error和Select(dest interface{}, ...) error最终也是失败告终,下面我们就谈谈database/sql与sqlx的使用以及如何解决上述的问题。

sql介绍

Go语言中database/sql包提供了保证SQL或类SQL数据库的泛用接口,并不提供具体的数据库驱动。使用database/sql包时必须注入(至少)一个数据库驱动,go使用的数据库驱动为go-sql-driver/mysql。

1、创建文件夹目录,下载依赖(建议使用go mod 模式)

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

2、链接数据库

函数:

func Open(driverName, dataSourceName string) (*DB, error) #Open打开一个dirverName指定的数据库比如mysql,dataSourceName指定数据源,数据库账号:密码@tcp(地址:端口)/数据库名;。

实现:

/*** @program: Go** @description:链接数据库** @author: Mr.chen** @create: 2020-09-04 16:25**/package mainimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql")var Db *sql.DBfunc initDb() (err error) { Db, err = sql.Open("mysql", "root:root@tcp(localhost:3306)/go_test") if err != nil { return err } defer Db.Close()  return nil}func main() { err := initDb() if err !=nil { panic(err.Error()) } fmt.Println("初始化数据库成功")
}

上述代码defer Db.Close()放在err后面实际是为了发生连接错误时候不执行Db.Close()函数,通常我们申请资源的时候都会用到defer xx.Close()让程序在退出时进行关闭 。即使我们放在err前面,代码也不会报错,假如连接数据库中出现故障,有两种情况,一申请出连接,但其不可用;另一种则是未申请到连接,通常连接获得的返回值是nil,如果是先执行defer xx.Close()的话,在第二种情况下,可能会在连接为nil的时候依然执行了Db.Close(),假如这个Close()函数保护不够充分,则容易会产生panic,其中上述做法是不会主动检查数据库连接的真实性,即使数据库信息不存在,也是返回nil,检查数据源(dataSourceName)的数据是否真实有效,应该调用上述Db.Ping方法,上述做法是属于单例模式,有些人肯定会说为什么不用sync.Once之执行一次,因为数据库的连接涉及到数据库连接池的问题,其中database/sql库返回的DB对象可以安全地被多个协程并发使用,并且很好的维护其自身的空闲连接池。因此,Open函数应该仅被调用一次,很少甚至不需要关闭这个DB对象 。

改进:

/*** @program: Go** @description:go实现数据库连接** @author: Mr.chen** @create: 2020-09-04 17:22**/package mainimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql")var Db *sql.DBfunc initDb() (err error) { Db, err = sql.Open("mysql", "root:root@tcp(localhost:3306)/go_test") if err != nil { return err } err = Db.Ping() if err != nil { return err } Db.SetMaxOpenConns(100) // SetMaxOpenConns设置与数据库建立最大连接数。如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。如果n<=0,不会限制最大开启连接数,默认为0(无限制)。 Db.SetMaxIdleConns(16) // SetMaxIdleConns设置连接池中的最大闲置连接数。如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。如果n<=0,不会保留闲置连接。 return nil}func main() { err := initDb() if err !=nil { panic(err.Error()) } fmt.Println("初始化数据库成功")
}

3、新建表

函数:

func (db *DB) Exec(query string, args ...interface{}) (Result, error) // 执行sql语句,返回结果值Result,结果值如下type Result interface { // LastInsertId returns the integer generated by the database // in response to a command. Typically this will be from an // "auto increment" column when inserting a new row. Not all // databases support this feature, and the syntax of such // statements varies. LastInsertId() (int64, error)
// RowsAffected returns the number of rows affected by an // update, insert, or delete. Not every database or database // driver may support this. RowsAffected() (int64, error)}

实现:

/*** @program: Go** @description:创建表** @author: Mr.chen** @create: 2020-09-04 16:55**/package mainimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql")
var tbUser = `CREATE TABLE tb_user ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL DEFAULT '', pwd varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (id)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;`type User struct { Id string Name string Pwd string}

var Db *sql.DBfunc initDb() (err error) { Db, err = sql.Open("mysql", "root:root@tcp(localhost:3306)/go_test") if err != nil { return err } err = Db.Ping() if err != nil { return err } Db.SetMaxOpenConns(100) // SetMaxOpenConns设置与数据库建立最大连接数。如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。如果n<=0,不会限制最大开启连接数,默认为0(无限制)。 Db.SetMaxIdleConns(16) // SetMaxIdleConns设置连接池中的最大闲置连接数。如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。如果n<=0,不会保留闲置连接。 return nil}func main() { err := initDb() if err !=nil { panic(err.Error()) } fmt.Println("初始化数据库成功")
_, err = Db.Exec(tbUser) if err != nil { fmt.Printf("create table failed, err:%v\n", err) return }
fmt.Println("create table success")}

4、插入数据库

函数:

// Db.Execfunc (db *DB) Exec(query string, args ...interface{}) (Result, error)// insertUser插入数据func insertUser (name,pwd string) (err error){ sqlStr := "insert into tb_user(name,pwd) values(?,?)" _, err = Db.Exec(sqlStr, name, pwd) if err != nil { return err } return nil}

实现:

/*** @program: Go** @description:创建表** @author: Mr.chen** @create: 2020-09-04 16:55**/package mainimport ( "crypto/md5" "database/sql" "encoding/hex" "fmt" _ "github.com/go-sql-driver/mysql")
var tbUser = `CREATE TABLE tb_user ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL DEFAULT '', pwd varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (id)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;`type User struct { Id string Name string Pwd string}

var Db *sql.DBfunc initDb() (err error) { Db, err = sql.Open("mysql", "root:root@tcp(localhost:3306)/go_test") if err != nil { return err } err = Db.Ping() if err != nil { return err } Db.SetMaxOpenConns(100) // SetMaxOpenConns设置与数据库建立最大连接数。如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。如果n<=0,不会限制最大开启连接数,默认为0(无限制)。 Db.SetMaxIdleConns(16) // SetMaxIdleConns设置连接池中的最大闲置连接数。如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。如果n<=0,不会保留闲置连接。 return nil}func insertUser (name,pwd string) (err error){ sqlStr := "insert into tb_user(name,pwd) values(?,?)" _, err = Db.Exec(sqlStr, name, pwd) if err != nil { return err } return nil}func Md5Encode(data string) string{ h := md5.New() h.Write([]byte(data)) // 需要加密的字符串为 123456 cipherStr := h.Sum(nil) return hex.EncodeToString(cipherStr)}func main() { err := initDb() if err !=nil { panic(err.Error()) } fmt.Println("初始化数据库成功") password := Md5Encode("123456") err = insertUser("Mr.chen",password) if err != nil { fmt.Printf("insert table failed, err:%v\n", err) return }
fmt.Println("insert table success")}

5、查询:

a、查询列表:

函数:

// Db.Queryfunc (db *DB) Query(query string, args ...interface{}) (*Rows, error)// 查询多条数据func queryAllUser()(userkList []User, err error) { sqlStr := "select id,name,pwd from tb_user where id > ?" rows, err := Db.Query(sqlStr, 0) if err != nil { return nil, err } // 非常重要:关闭rows释放持有的数据库链接 defer rows.Close() // 循环读取结果集中的数据 for rows.Next() { var user User err := rows.Scan(&user.Id, &user.Name, &user.Pwd) if err != nil { return userkList, err } userkList = append(userkList, user) } return userkList, nil}

b、查询单条数据

函数:

// Db.QueryRowfunc (db *DB) QueryRow(query string, args ...interface{}) *Row// 查询多条数据func queryAllUser()(userkList []User, err error) { sqlStr := "select id,name,pwd from tb_user where id > ?" rows, err := Db.Query(sqlStr, 0) if err != nil { return nil, err } // 非常重要:关闭rows释放持有的数据库链接 defer rows.Close() // 循环读取结果集中的数据 for rows.Next() { var user User err := rows.Scan(&user.Id, &user.Name, &user.Pwd) if err != nil { return userkList, err } userkList = append(userkList, user) } return userkList, nil}// 查询单条数据func queryUserInfo()(userInfo User, err error) { sqlStr := "select id,name,pwd from tb_user where id > ?" rows := Db.QueryRow(sqlStr, 0) err = rows.Scan(&userInfo.Id, &userInfo.Name, &userInfo.Pwd) if err != nil { return userInfo, err } return userInfo, nil}

实践:

/*** @program: Go** @description:创建表** @author: Mr.chen** @create: 2020-09-04 16:55**/package mainimport ( "crypto/md5" "database/sql" "encoding/hex" "fmt" _ "github.com/go-sql-driver/mysql")

type User struct { Id string Name string Pwd string}

var Db *sql.DBfunc initDb() (err error) { Db, err = sql.Open("mysql", "root:root@tcp(localhost:3306)/go_test") if err != nil { return err } err = Db.Ping() if err != nil { return err } Db.SetMaxOpenConns(100) // SetMaxOpenConns设置与数据库建立最大连接数。如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。如果n<=0,不会限制最大开启连接数,默认为0(无限制)。 Db.SetMaxIdleConns(16) // SetMaxIdleConns设置连接池中的最大闲置连接数。如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。如果n<=0,不会保留闲置连接。 return nil}// 查询多条数据func queryAllUser()(userList []User, err error) { sqlStr := "select id,name,pwd from tb_user where id > ?" rows, err := Db.Query(sqlStr, 0) if err != nil { return userList, err } // 非常重要:关闭rows释放持有的数据库链接 defer rows.Close() // 循环读取结果集中的数据 for rows.Next() { var user User err := rows.Scan(&user.Id, &user.Name, &user.Pwd) if err != nil { return userList, err } userList = append(userList, user) } return userList, nil}// 查询单条数据func queryUserInfo()(userInfo User, err error) { sqlStr := "select id,name,pwd from tb_user where id > ?" rows := Db.QueryRow(sqlStr, 0) err = rows.Scan(&userInfo.Id, &userInfo.Name, &userInfo.Pwd) if err != nil { return userInfo, err } return userInfo, nil}func main() { err := initDb() if err !=nil { panic(err.Error()) } fmt.Println("初始化数据库成功") // 查询多条记录 userkList,err := queryAllUser() if err != nil { fmt.Printf("queryAllUser failed, err:%v\n", err) return } for _,v := range userkList{ fmt.Println("名称:", v.Name) } // 查询单条记录 userkInfo,err := queryUserInfo() if err != nil { fmt.Printf("queryUserInfo failed, err:%v\n", err) return } fmt.Println("查询单条记录名称:", userkInfo.Name)}

6、删除数据

函数:

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

实践:

// 删除数据func deleteUser(id int) error { sqlStr := "delete from tb_user where id = ?" ret, err := Db.Exec(sqlStr,id) if err != nil { return err } n, err := ret.RowsAffected() // 操作影响的行数 if err != nil { return err } fmt.Printf("delete success, affected rows:%d\n", n) return nil}

7、更新数据

函数:

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

实践:

// 更新数据func updateUserInfo(id int,name string) error { sqlStr := "update tb_user set name=? where id = ?" ret, err := Db.Exec(sqlStr, name,id) if err != nil { return err } n, err := ret.RowsAffected() // 操作影响的行数 if err != nil { return err } fmt.Printf("update success, affected rows:%d\n", n) return nil}

8、mysql预处理Prepare

mysql预处理主要是将SQL语句分成两部分,命令与数据两部分,先将命令发送给mysql服务端,再将数据发送给mysql服务端进行占位符替换,mysql执行完整的SQL返回结果,普通的SQL语句执行是将客户端SQL语句进行占位符替换得到完整的SQL语句,一整串发送给SQL服务端,服务端返回结果 。

使用预处理可以预防SQL注入,一次编译多次使用,提高性能,下面我们看看SQL是如何注入的。

select id,name,pwd from tb_user where name =?

当我们获取参数?为

"***" or id>0

完整的SQL为

select id,name,pwd from tb_user where name="***" or id>0

预处理函数:

func (db *DB) Prepare(query string) (*Stmt, error) 

预处理实践:

type InsertData map[string]string// 预处理查询func prepareQueryUser(id int) (userList [] User , err error) { sqlStr := "select id, name, pwd from tb_user where id > ?" stmt, err := Db.Prepare(sqlStr) if err != nil { return userList,err } defer stmt.Close() rows, err := stmt.Query(id) if err != nil { return userList,err } defer rows.Close() // 循环读取结果集中的数据 for rows.Next() { var user User err := rows.Scan(&user.Id, &user.Name, &user.Pwd) if err != nil { return userList, err } userList = append(userList, user) } return userList, nil}// 预处理插入func prepareInsertMoreUser(args []InsertData) (err error) { sqlStr := "insert into tb_user(name, pwd) values (?,?)" stmt, err := Db.Prepare(sqlStr) if err != nil { return err } defer stmt.Close() for _,v := range args { _, err = stmt.Exec(v["name"], v["pwd"]) if err != nil { return err } } return nil}// 预处理删除func prepareDeleteMoreUser(args []string) (err error) { sqlStr := "delete from tb_user where name = ?" stmt, err := Db.Prepare(sqlStr) if err != nil { return err } defer stmt.Close() for _,v := range args { _, err = stmt.Exec(v) if err != nil { return err } } return nil}// 预处理更新func prepareUpdateMoreUser(args []InsertData) (err error) { sqlStr := "update tb_user set pwd = ? where name = ?" stmt, err := Db.Prepare(sqlStr) if err != nil { return err } defer stmt.Close() for _,v := range args { _, err = stmt.Exec(v["pwd"],v["name"]) if err != nil { return err } } return nil}// main// 预处理查询多条记录userkLists,err := prepareQueryUser(0)if err != nil { fmt.Printf("prepareQueryUser failed, err:%v\n", err) return}for _,v := range userkLists{ fmt.Println("预处理查询的名称:", v.Name)}
// 预处理插入多条记录InsertUserDatas := make([]InsertData,0)for i:=0;i<9;i++ { InsertUserData := make(InsertData) InsertUserData["name"] = fmt.Sprintf("text_%d",i) InsertUserData["pwd"] = Md5Encode(fmt.Sprintf("abc123%d",i)) InsertUserDatas = append(InsertUserDatas,InsertUserData)}err = prepareInsertMoreUser(InsertUserDatas)if err != nil { fmt.Printf("prepareInsertMoreUser failed, err:%v\n", err)}fmt.Println("prepareInsertMoreUser success.")
// 预处理删除多条记录deletwUserDatas := make([]string,0)for i:=0;i<9;i++ { if i%2 == 0 { fmt.Println(fmt.Sprintf("text_%d",i)) deletwUserDatas = append(deletwUserDatas,fmt.Sprintf("text_%d",i)) }}err = prepareDeleteMoreUser(deletwUserDatas)if err != nil { fmt.Printf("prepareDeleteMoreUser failed, err:%v\n", err)}fmt.Println("prepareDeleteMoreUser success.")
// 预处理更新多条记录updateUserDatas := make([]InsertData,0)for i:=0;i<9;i++ { if i%2 != 0 { updateUserData := make(InsertData) updateUserData["name"] = fmt.Sprintf("text_%d",i) updateUserData["pwd"] = Md5Encode(fmt.Sprintf("123456%d",i)) updateUserDatas = append(updateUserDatas,updateUserData) }
}err = prepareUpdateMoreUser(updateUserDatas)if err != nil { fmt.Printf("prepareUpdateMoreUser failed, err:%v\n", err)}fmt.Println("prepareUpdateMoreUser success.")

9、事务

事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),简称ACID。

函数:

// 开始事务func (db *DB) Begin() (*Tx, error)// 提交事务func (tx *Tx) Commit() error// 回滚func (tx *Tx) Rollback() error

实践:

// 事务提交更新数据func transactionUser(args []InsertData) error { sqlStr := "update tb_user set pwd = ? where name = ?" tx,err := Db.Begin() if err != nil { tx.Rollback() return err } stmt, err := Db.Prepare(sqlStr) if err != nil { tx.Rollback() return err } defer stmt.Close() for _,v := range args { _, err = stmt.Exec(v["pwd"],v["name"]) if err != nil { tx.Rollback() return err } } tx.Commit() return nil}// main// 预处理事务操作tranUserDatas := make([]InsertData,0)for i:=0;i<9;i++ { if i%3 == 0 { tranUserData := make(InsertData) tranUserData["name"] = fmt.Sprintf("text_%d",i) tranUserData["pwd"] = fmt.Sprintf("123456%d",i) tranUserDatas = append(tranUserDatas,tranUserData) }}err = transactionUser(tranUserDatas)if err != nil { fmt.Printf("transactionUser failed, err:%v\n", err)}fmt.Println("transactionUser success.")

本文介绍Go语言操作mysql先到这里,下一次我们一起来学习一下sqlx,一个比Go语言内置database/sql更丰富的库。

10、附加

a、上述群友问题解决方法:

/*** @program: Go** @description:原生与sqlx结合** @author: Mr.chen** @create: 2020-09-04 10:23**/package mainimport ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx")type Book struct { ID int64 `db:"id"` Title string `db:"title"` Price float64 `db:"price"` T2 string}

// 跟数据库相关的操作var db *sqlx.DB
func initDB()(err error){ dsn := "root:root@tcp(127.0.0.1:3306)/go_test" db, err = sqlx.Connect("mysql", dsn) if err != nil { return err } db.SetMaxOpenConns(100) db.SetMaxIdleConns(16) return}
// 查数据func queryAllBook()(bookLists []Book, err error){ stmt, err := db.Prepare("select title,id as t2 from book where id >?") if err != nil { panic(err) } rows, err := stmt.Query(0) if err != nil { panic(err) } defer stmt.Close() defer rows.Close() var bookList Book for rows.Next(){ err := rows.Scan(&bookList.Title,&bookList.T2) if err != nil { panic(err) } bookLists=append(bookLists,bookList) } return}
func main(){ initDB() bookList,_:=queryAllBook(); fmt.Printf("bookList: %#v\n", bookList[1].T2)}

b、数据操作类:

/*** @program: Go** @description:原生与sqlx结合** @author: Mr.chen** @create: 2020-09-04 10:23**/package mysqlimport ( "database/sql" "fmt" "log" "os" _ "github.com/go-sql-driver/mysql")
var db *sql.DB// 连接数据库func InitDBConn() { dsn := "root:root@tcp(127.0.0.1:3306)/go_test" db, _ = sql.Open("mysql", dsn) db.SetMaxOpenConns(1000) err := db.Ping() if err != nil { fmt.Println("Failed to connect to mysql, err:" + err.Error()) os.Exit(1) }}
// DBConn : 返回数据库连接对象func DBConn() *sql.DB { return db}// 将sql.Rows返回数组func ParseRows(rows *sql.Rows) []map[string]interface{} { columns, _ := rows.Columns() scanArgs := make([]interface{}, len(columns)) values := make([]interface{}, len(columns)) for j := range values { scanArgs[j] = &values[j] }
record := make(map[string]interface{}) records := make([]map[string]interface{}, 0) for rows.Next() { //将行数据保存到record字典 err := rows.Scan(scanArgs...) checkErr(err)
for i, col := range values { if col != nil { record[columns[i]] = col } } records = append(records, record) } return records}// 报错func checkErr(err error) { if err != nil { log.Fatal(err) panic(err) }}


以上是关于Go语言操作mysql指南的主要内容,如果未能解决你的问题,请参考以下文章

4. 编程规范和编程安全指南--go语言

腾讯代码安全指南开源,涉及 C/C++Go 等六门编程语言

腾讯代码安全指南开源,涉及 C/C++Go 等六门编程语言

你知道的Go切片扩容机制可能是错的

go语言代码规范指南

腾讯发布了Go语言代码安全指南