一、前言
golang适合写中间件,今年一直在做数据库相关的工作,用的也是golang,短平快的写过一波代码之后开始回过头来做一个总结。在golang中,提供了标准的数据库接口database/sql包。
二、database/sql简介
SQL 是应用程序和数据库之间的中间层,应用程序在多数情况下都不需要关心底层数据库的实现,它们只关心 SQL 查询返回的数据。我们可以使用相同的 SQL 语言查询关系型数据库,所有关系型数据库的客户端都需要实现如下所示的驱动接口:
type Driver interface {
Open(name string) (Conn, error)
}
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
database/sql/driver.Driver 接口中只包含一个 Open
方法,该方法接收一个数据库连接串作为输入参数并返回一个特定数据库的连接,作为参数的数据库连接串是数据库特定的格式,这个返回的连接仍然是一个接口,整个标准库中的全部接口可以构成如下所示的树形结构:
Go语言中的database/sql
包提供了保证SQL或类SQL数据库的泛用接口,并不提供具体的数据库驱动。使用database/sql
包时必须注入相应的数据库驱动。常见的数据库驱动如下:
Mysql: https://github.com/go-sql-driver/mysql
MyMysql: https://github.com/ziutek/mymysql
Postgres: https://github.com/lib/pq
Tidb: https://github.com/pingcap/tidb
SQLite: https://github.com/mattn/go-sqlite3
MsSql: https://github.com/denisenkom/go-mssqldb
三、代码分析
1、连接数据库。
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
// 定义一个全局对象db
var db *sql.DB
// 定义一个初始化数据库的函数
func initDB() (err error) {
// DSN:Data Source Name
dsn := "user:password@tcp(127.0.0.1:3306)/test"
// 不会校验账号密码是否正确
// 注意!!!这里不要使用:=,我们是给全局变量赋值,然后在main函数中使用全局变量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.Ping()没有问题之后,我们就可以使用db去操作了,注意的是,这个返回的db可以安全的被多个goroutine同时使用,并会维护自身的闲置连接池。这样一来,Open函数只需调用一次。很少需要关闭db。
准备数据:
新建test数据库,person、place 表
CREATE TABLE `person` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(260) DEFAULT NULL,
`sex` varchar(260) DEFAULT NULL,
`email` varchar(260) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
CREATE TABLE place (
country varchar(200),
city varchar(200),
telcode int
)ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
mysql> desc person;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| user_id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(260) | YES | | NULL | |
| sex | varchar(260) | YES | | NULL | |
| email | varchar(260) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> desc place;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| country | varchar(200) | YES | | NULL | |
| city | varchar(200) | YES | | NULL | |
| telcode | int(11) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
2、insert代码
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Person struct {
UserId int `db:"user_id"`
Username string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
type Place struct {
Country string `db:"country"`
City string `db:"city"`
TelCode int `db:"telcode"`
}
var Db *sqlx.DB
func init() {
database, err := sqlx.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("open mysql failed,", err)
return
}
Db = database
defer db.Close() // 注意这行代码要写在上面err判断的下面
}
func main() {
r, err := Db.Exec("insert into person(username, sex, email)values(?, ?, ?)", "stu001", "man", "stu01@qq.com")
if err != nil {
fmt.Println("exec failed, ", err)
return
}
id, err := r.LastInsertId()
if err != nil {
fmt.Println("exec failed, ", err)
return
}
fmt.Println("insert succ:", id)
}
3、select代码
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Person struct {
UserId int `db:"user_id"`
Username string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
type Place struct {
Country string `db:"country"`
City string `db:"city"`
TelCode int `db:"telcode"`
}
var Db *sqlx.DB
func init() {
database, err := sqlx.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("open mysql failed,", err)
return
}
Db = database
defer db.Close() // 注意这行代码要写在上面err判断的下面
}
func main() {
var person []Person
err := Db.Select(&person, "select user_id, username, sex, email from person where user_id=?", 1)
if err != nil {
fmt.Println("exec failed, ", err)
return
}
fmt.Println("select succ:", person)
}
4、update代码
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Person struct {
UserId int `db:"user_id"`
Username string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
type Place struct {
Country string `db:"country"`
City string `db:"city"`
TelCode int `db:"telcode"`
}
var Db *sqlx.DB
func init() {
database, err := sqlx.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("open mysql failed,", err)
return
}
Db = database
defer db.Close() // 注意这行代码要写在上面err判断的下面
}
func main() {
res, err := Db.Exec("update person set username=? where user_id=?", "stu0003", 1)
if err != nil {
fmt.Println("exec failed, ", err)
return
}
row, err := res.RowsAffected()
if err != nil {
fmt.Println("rows failed, ",err)
}
fmt.Println("update succ:",row)
}
5、delete代码
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Person struct {
UserId int `db:"user_id"`
Username string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
type Place struct {
Country string `db:"country"`
City string `db:"city"`
TelCode int `db:"telcode"`
}
var Db *sqlx.DB
func init() {
database, err := sqlx.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("open mysql failed,", err)
return
}
Db = database
defer db.Close() // 注意这行代码要写在上面err判断的下面
}
func main() {
/*
_, err := Db.Exec("delete from person where user_id=?", 1)
if err != nil {
fmt.Println("exec failed, ", err)
return
}
*/
res, err := Db.Exec("delete from person where user_id=?", 1)
if err != nil {
fmt.Println("exec failed, ", err)
return
}
row,err := res.RowsAffected()
if err != nil {
fmt.Println("rows failed, ",err)
}
fmt.Println("delete succ: ",row)
}
6、事务代码
golang MySQL事务应用:
1) import (“github.com/jmoiron/sqlx")
2) Db.Begin() 开始事务
3) Db.Commit() 提交事务
4) Db.Rollback() 回滚事务
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Person struct {
UserId int `db:"user_id"`
Username string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
type Place struct {
Country string `db:"country"`
City string `db:"city"`
TelCode int `db:"telcode"`
}
var Db *sqlx.DB
func init() {
database, err := sqlx.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("open mysql failed,", err)
return
}
Db = database
}
func main() {
conn, err := Db.Begin()
if err != nil {
fmt.Println("begin failed :", err)
return
}
r, err := conn.Exec("insert into person(username, sex, email)values(?, ?, ?)", "stu001", "man", "stu01@qq.com")
if err != nil {
fmt.Println("exec failed, ", err)
conn.Rollback()
return
}
id, err := r.LastInsertId()
if err != nil {
fmt.Println("exec failed, ", err)
conn.Rollback()
return
}
fmt.Println("insert succ:", id)
r, err = conn.Exec("insert into person(username, sex, email)values(?, ?, ?)", "stu001", "man", "stu01@qq.com")
if err != nil {
fmt.Println("exec failed, ", err)
conn.Rollback()
return
}
id, err = r.LastInsertId()
if err != nil {
fmt.Println("exec failed, ", err)
conn.Rollback()
return
}
fmt.Println("insert succ:", id)
conn.Commit()
}
四、预处理
普通SQL语句执行过程:
- 客户端对SQL语句进行占位符替换得到完整的SQL语句。
- 客户端发送完整SQL语句到MySQL服务端
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
预处理执行过程:
- 把SQL语句分成两部分,命令部分与数据部分。
- 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
- 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
为什么要预处理?
- 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
- 避免SQL注入问题。
mysql预处理代码:
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
var db *sql.DB
var err error
func initDB() {
if db, err = sql.Open("postgres", "postgres://postgres:zgghyys123@127.0.0.1:5432/postgres?sslmode=disable"); err != nil {
panic(err)
}
if err = db.Ping(); err != nil {
panic(err)
}
}
type heroes struct {
id int64
name string
age string
hp int64
attack string
role string
ultimate string
country string
}
func insertData(){
insertQuery := "insert into anime.overwatch(id, name, age) values($1, $2, $3)"
//Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。
stmt, _ := db.Prepare(insertQuery)
defer stmt.Close()
//传入参数
ret, err := stmt.Exec(100, "hanser", 26)
if err != nil {
panic(err)
}
if rowCount, err := ret.RowsAffected(); err != nil {
panic(err)
} else {
fmt.Println(rowCount) //1
}
}
func main() {
initDB()
insertData()
}
//我们这里可以多次执行Exec,会多次插入记录