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 main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var Db *sql.DB
func 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 main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var Db *sql.DB
func 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 main
import (
"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.DB
func 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.Exec
func (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 main
import (
"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.DB
func 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.Query
func (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.QueryRow
func (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 main
import (
"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.DB
func 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 main
import (
"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 mysql
import (
"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指南的主要内容,如果未能解决你的问题,请参考以下文章