假期关于mysql相关的操作学习,总结记录,以飨读者
Mysql是业届常用的关系型数据库,本文以go语言学习如何操作mysql数据库。
主要包括以下几个部分:
- Ping测试mysql
- mysql连接池相关参数配置
- CURD相关操作
- mysql超时操作
- mysql事务操作
完整代码参考我的github工程go语言操作mysql
零、环境准备及go工程创建
环境:centos、mysql8.0 首先保证mysql服务正常,可以使用sql相关命令进行连接测试,创建几条测试数据方便后续操作。
mysql -u你的名字 -p你的密码
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;
INSERT INTO user (name , age) VALUES
('张三', '18'),
('李四', '28'),
('翠花', '38');
select * from user;
在工作目录建立go工程,同时使用go mod 进行初始化
mkdir go-sql-database-example
go mod init github.com/wsqyouth/tech_note/go-sql-database-example
Go语言sql包提供了操作SQL的接口,但并不提供具体数据库驱动,因此我们须引入第三方的驱动包。
go get -u github.com/go-sql-driver/mysql
touch main.go
至此,我们拥有了测试数据、安装了第三方驱动包、同时创建了go工程,让我们开始吧!
一、Ping测试mysql
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
pingDemo()
}
func pingDemo() {
dsn := "你的名字:你的密码@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("could not connect to database: %v", err)
}
if err := db.Ping(); err != nil {
log.Fatalf("unable to reach database: %v", err)
}
fmt.Println("database is reachable")
defer db.Close()
}
我们运行该文件,可以发现测试ok:
二、mysql连接池相关参数配置
为了方便测试后续CURD各功能函数,本着高内聚、低耦合 的原则,我们定义一个全局变量db ,用来保存连接数据库的句柄。将上面的示例代码拆分为独立的函数initDB ,在程序启动时完成初始化,其他功能函数就可以直接使用全局变量了。 初始化连接函数
func initDB() (err error) {
dsn := "你的账号:你的密码@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
db, err = sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("could not connect to database: %v", err)
}
if err := db.Ping(); err != nil {
log.Fatalf("unable to reach database: %v", err)
}
db.SetMaxIdleConns(5)
db.SetMaxOpenConns(10)
db.SetConnMaxIdleTime(1 * time.Second)
db.SetConnMaxLifetime(30 * time.Second)
fmt.Println("init succ")
return nil
}
关于数据库的连接配置参考,这里我直接借鉴国外的一篇博客Using an SQL Database in Go进行摘录:
- Maximum Open Connections are the maximum number of parallel connections that can be made to the database at any time.
- Maximum Idle Connections are the maximum number of connections that can be inactive at any time. A connection is idle, if no queries are being executed on it. This can happen if the number of queries being executed are less than the current pool of connections can handle.
- Idle Connection Timeout is the maximum time for which any given connection can be idle. After this time had elapsed, the connection to the database will be closed.
*** Connection Lifetime** is the maximum amount of time that a connection can be open (regardless of whether it’s idle or not). 这几个参数是可以灵活设置的,但是总体上我们要考虑: - 最好让一小部分连接处于空闲状态,以应对查询吞吐量的突然峰值
- 应该根据我们的数据库服务器和应用程序服务器的网络容量来设置打开连接的最大数量,其中较小的将是限制因素。
三、mysql相关CURD操作
其实就是CURD的封装,记得我们使用了上文提及的全局变量句柄db ,代码如下:
func queryRowDemo(id int) {
sqlStr := "select id,name,age from user where id = ? limit 1"
row := db.QueryRow(sqlStr, id)
var u user
if err := row.Scan(&u.id, &u.name, &u.age); err != nil {
log.Fatalf("could not scan row: %v", err)
}
fmt.Printf("user:%+v\n", u)
}
func queryMultiRowDemo() {
sqlStr := "select id,name,age from user where id > ?"
rows, err := db.Query(sqlStr, 1)
if err != nil {
log.Fatalf("could not execute query: %v", err)
}
users := []user{}
for rows.Next() {
var u user
if err := rows.Scan(&u.id, &u.name, &u.age); err != nil {
log.Fatalf("could not scan row: %v", err)
}
users = append(users, u)
}
fmt.Printf("len:%v,users:%+v\n", len(users), users)
}
func insertRowDemo() {
newUser := user{
name: "王五",
age: 23,
}
sqlStr := "insert into user(name,age) values (?,?)"
result, err := db.Exec(sqlStr, newUser.name, newUser.age)
if err != nil {
log.Fatalf("could not execute query: %v", err)
}
theID, err := result.LastInsertId()
if err != nil {
log.Fatalf("get LastInsertId falid. err:%v", err)
}
fmt.Printf("insert succ. the id is %d\n", theID)
}
func updateRowDemo() {
newUser := user{
name: "王五",
age: 28,
}
sqlStr := "update user set age=? where name = ?"
result, err := db.Exec(sqlStr, newUser.age, newUser.name)
if err != nil {
log.Fatalf("update falid. err:%v", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
log.Fatalf("could not get affected rows: %v", err)
}
fmt.Println("update succ, affected rows:%v", rowsAffected)
}
func deleteRowDemo() {
newUser := user{
name: "张三",
}
sqlStr := "delete from user where name = ?"
result, err := db.Exec(sqlStr, newUser.name)
if err != nil {
log.Fatalf("update falid. err:%v", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
log.Fatalf("could not get affected rows: %v", err)
}
fmt.Println("delete succ, affected rows:%v", rowsAffected)
}
四、超时相关功能
sql的慢查询也是一个需要考虑的问题,如果某个请求的查询比较耗时,就会阻塞其他请求拿到连接,因此我们需要在连接sql时考虑到如果超时则取消功能。这里我们使用context进行测试:
func timeOutDemo() {
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 300*time.Millisecond)
_, err := db.QueryContext(ctx, "select sleep(1)")
if err != nil {
log.Fatalf("could not execute query: %v", err)
}
}
我们设置超时时间为300ms,但是这条sql语句需要1s才能执行完成,运行该demo预期输出:
五、mysql事务操作
事务的操作也是需要考虑的,要么一起成功、要么一起失败,因此这里的demo示例如下:
func transactionDemo() {
tx, err := db.Begin()
if err != nil {
if tx != nil {
tx.Rollback()
}
fmt.Printf("begin trans failed. err:%v\n", err)
return
}
sqlStr := "Update user set age = 34 where id = ?"
result, err := tx.Exec(sqlStr, 2)
if err != nil {
if tx != nil {
tx.Rollback()
}
fmt.Printf("update trans failed. err:%v\n", err)
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
if tx != nil {
tx.Rollback()
}
log.Fatalf("could not get affected rows: %v", err)
return
}
fmt.Println(rowsAffected)
if rowsAffected == 1 {
fmt.Println("only one user affect. trans commit")
tx.Commit()
} else {
fmt.Println("affect num err. trans rollback")
tx.Rollback()
}
fmt.Println("exec trans end")
}
六、展望
至此,Go语言操作mysql常见的操作就算测试完了,但是这只是入门,站在这里,你可能会考虑一下问题:
- sql预处理和sql注入问题
- sql连接池监控问题
- 本文使用的是原生sql处理,是否有orm框架来解决新增字段扩展性问题
- 事务、分布式事务又将如何开展
文章参考
本文工程重点参考文章,特此感谢 国外谷歌工程师的文章:该文章是go语言操作pgsql,但是工程风格及讲解一流,引人入胜 Using an SQL Database in Go A Guide On SQL Database Transactions In Go 李文周博客-go操作mysql
|