Go操作MySQL
安装: go get -u github.com/go-sql-driver/mysql
GO语言的操作数据库的驱动原生支持连接池, 并且是并发安全的 标准库没有具体的实现 只是列出了一些需要的第三方库实现的具体内容
//第一次连接MySQL成功 | |
package main | |
import ("database/sql"_ "github.com/go-sql-driver/mysql" // _想当于init()初始化"log" | |
) | |
func main() {// root 用户名 1qa2ws3ed是密码 后边的书ip:port gouse 库名 | |
dsn := "root:1qa2ws3ed@tcp(127.0.0.1:3306)/gouse" | |
db, err := sql.Open("mysql", dsn)if err != nil {panic(err)}// ping是尝试连接MySQL数据库 | |
if err = db.Ping(); err != nil{panic(err)} | |
log.Fatalln("Mysql数据库连接成功") | |
} |
- Go调用MySQL封装成函数
package main | |
import ("database/sql""encoding/json""fmt" | |
_ "github.com/go-sql-driver/mysql" | |
) | |
var db *sql.DB | |
func InitDB() (err error) { | |
dsn := "root:1qa2ws3ed@tcp(127.0.0.1:3306)/gouse" | |
db, err = sql.Open("mysql", dsn)CheckErr(err) | |
err = db.Ping()CheckErr(err) | |
fmt.Println("数据库连接成功...")// 设置数据库连接池最大连接数 | |
db.SetConnMaxLifetime(10) | |
//设置最大闲置连接数 | |
db.SetMaxIdleConns(5) | |
return | |
} | |
type data struct { | |
Username string `json:"username"` | |
Password string `json:"password"` | |
} | |
func main() { | |
err := InitDB()CheckErr(err) | |
query, err := db.Query("select username, password from test")CheckErr(err) | |
for query.Next(){ | |
line := data{}// 查询数据的时候必须要调用scan方法如果 没有 使用scan 连接通道一直保持连接 无法释放连接 | |
_ = query.Scan(&line.Username, &line.Password) | |
fmt.Println(line) | |
dataDic := map[string]string{"username": line.Username,"password": line.Password,} | |
marshal, _ := json.Marshal(dataDic) | |
fmt.Println(string(marshal))} | |
} | |
func CheckErr(err error) {if err != nil { | |
fmt.Println(err)panic(err)} | |
} |
- GO—MySQL的增删改查
package main | |
import ("database/sql""encoding/json""fmt""time" | |
_ "github.com/go-sql-driver/mysql" | |
) | |
var db *sql.DB | |
// InitDB 数据库连接初始化 | |
func InitDB() (err error) { | |
dsn := "root:1qa2ws3ed@tcp(127.0.0.1:3306)/gouse" | |
db, err = sql.Open("mysql", dsn)CheckErr(err) | |
err = db.Ping()CheckErr(err) | |
fmt.Println("数据库连接成功...")// 设置数据库连接池最大连接数 | |
db.SetConnMaxLifetime(10) | |
//设置最大闲置连接数 | |
db.SetMaxIdleConns(5) | |
return | |
} | |
type data struct { | |
Username string `json:"username"` | |
Password string `json:"password"` | |
} | |
// SelectQuery 查询函数 | |
func SelectQuery() { | |
sqlStr := "select username, password from test where id > ?" | |
query, err := db.Query(sqlStr, 1)CheckErr(err) | |
defer query.Close() | |
fmt.Printf("现在是北京时间 %s , 你今天进步了吗?\n", time.Now().Format("2006-01-02 15:04:05")) | |
for query.Next() { | |
line := data{}// 查询数据的时候必须要调用scan方法如果 没有 使用scan 连接通道一直保持连接 无法释放连接 | |
_ = query.Scan(&line.Username, &line.Password)//fmt.Println(line) | |
dataDic := map[string]string{"username": line.Username,"password": line.Password,} | |
marshal, _ := json.Marshal(dataDic) | |
fmt.Printf("查询到的数据为 %s\n", string(marshal))} | |
} | |
// InsertQuery 插入数据 | |
func InsertQuery() {// sql 语句 | |
sqlStr := `insert into test (username,password) values ("kuQi", "123qwe")` | |
result, err := db.Exec(sqlStr)CheckErr(err) | |
id, err := result.LastInsertId()CheckErr(err) | |
fmt.Printf("插入成功数据的id为 %v", id) | |
} | |
// UpdateQuery 更新数据函数 | |
func UpdateQuery(dataField string, user string) { | |
sqlStr := `update test set password=? where username=?` | |
result, err := db.Exec(sqlStr, dataField, user)CheckErr(err) | |
rowsAffected, err := result.RowsAffected()CheckErr(err) | |
fmt.Printf("被更新字段的id为%d\n", rowsAffected) | |
} | |
// DeleteQuery 删除 | |
func DeleteQuery(id int) { | |
sqlStr := `delete from test where id=?` | |
result, err := db.Exec(sqlStr, id)CheckErr(err) | |
rowsAffected, err := result.RowsAffected()CheckErr(err)if rowsAffected == 0 { | |
fmt.Printf("没有匹配到要删除的id=%d数据", id)return} | |
fmt.Printf("删除数据库的id为%d", id) | |
} | |
//CheckErr 异常捕获函数 | |
func CheckErr(err error) {if err != nil { | |
fmt.Println(err)panic(err)} | |
} | |
// main 主函数 所有函数的入口 | |
func main() { | |
err := InitDB()CheckErr(err) | |
//InsertQuery()UpdateQuery("hahaGolang123", "kuQi")SelectQuery()DeleteQuery(5) | |
} |
- MySQL的预处理
什么是预处理? | |
普通SQL语句执行过程: | |
1.客户端对SQL语句进行占位符的替换得到了完整的SQL语句 | |
2.客户端发送完整SQL语句到MySQL服务端 | |
3.MySQL服务端执行完整的SQL语句并将结果返回终端 | |
预处理的执行过程 | |
1.先把SQL语句拆分成两部分,SQL语句部分和参数部分 | |
2.先把SQL语句部分发送给MySQL服务端进行SQL预处理 | |
3.然后参数部分发送给MySQL服务端,MySQL对SQL语句进行拼接 | |
4.MySQL服务端执行完整的SQL语句返回结果 | |
为什么要进行预处理? | |
1.为了优化MySQL服务器重复执行SQL的方法。可以执行服务器的性能,提前让服务器编译,一次编译多次执行,节省后续重复编译的成本 | |
2.并且避免SQL注入 |
- Go实现MySQL预处理
// prepare方法现将SQL发送到MySQL服务端, 返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令 ; 命令也就是SQL语句 | |
// PrepareInsert 预处理执行插入语句 | |
func PrepareInsert() { | |
defer wg.Done() | |
sqlStr := `insert into test (username, password) values (?, ?)`// - 预处理 stmt 就是编译好的sql语句 之后直接传递参数即可 | |
stmt, err := db.Prepare(sqlStr)var u1 = uuid.Must(uuid.NewV4())CheckErr(err) | |
defer stmt.Close() | |
i := rand.Int() | |
username := fmt.Sprintf("yonghuming%d", i) | |
result, err := stmt.Exec(username, u1.String()[:10])CheckErr(err) | |
rowsAffected, err := result.LastInsertId()CheckErr(err) | |
fmt.Printf("成功插入id=%d条数据\n", rowsAffected) | |
} |
- Go语言实现MySQL实现事务操作
// go语言中使用一下三个方法实现MySQL中的事务操作, 开始事务 | |
func (db *DB) Begin()(*Tx, error) | |
// 提交事务 相当与Python中的conn.commit() | |
func (tx *Tx) Commit() error | |
// 回滚事务 | |
func (tx *Tx) Rollback() error | |
package main | |
import ("database/sql""fmt" | |
_ "github.com/go-sql-driver/mysql" | |
) | |
var db *sql.DB | |
type data struct { | |
Username string `json:"username"` | |
Password string `json:"password"` | |
} | |
// InitDB 数据库连接初始化 | |
func InitDB() (err error) { | |
dsn := "root:1qa2ws3ed@tcp(127.0.0.1:3306)/gouse" | |
db, err = sql.Open("mysql", dsn)CheckErr(err) | |
err = db.Ping()CheckErr(err) | |
fmt.Println("数据库连接成功...")// 设置数据库连接池最大连接数 | |
db.SetMaxOpenConns(100) | |
//设置最大闲置连接数 | |
db.SetMaxIdleConns(5) | |
return | |
} | |
//CheckErr 异常捕获函数 | |
func CheckErr(err error) {if err != nil { | |
fmt.Println(err)panic(err)} | |
} | |
// TranSaCtIon MySQL的事务操作 | |
func TranSaCtIon() {// 开启事务 | |
tx, err := db.Begin()CheckErr(err) | |
// 执行多个SQL操作 | |
sqlStr := `update test set id=id+100000 where password=?` | |
result, err := tx.Exec(sqlStr, "07f70f7e-4")CheckErr(err) | |
id, err := result.LastInsertId()if err != nil {// 语句回滚 | |
err := tx.Rollback() | |
fmt.Println("事务回滚")CheckErr(err) | |
} | |
fmt.Printf("修改后的id为%d\n", id) | |
} | |
func main() { | |
err := InitDB()CheckErr(err)TranSaCtIon() | |
} | |
- sqlx使用
第三方库sqlx能够简化操作,提高开发效率
安装go get github.com/jmoiron/sqlx
package main | |
import ("fmt" | |
_ "github.com/go-sql-driver/mysql""github.com/jmoiron/sqlx" | |
) | |
var db *sqlx.DB | |
// InitDB 数据库初始化 | |
func InitDB() (err error) { | |
dsn := "root:1qa2ws3ed@tcp(127.0.0.1:3306)/gouse" | |
db, err = sqlx.Connect("mysql", dsn)CheckErr(err) | |
db.SetMaxOpenConns(50) | |
db.SetMaxIdleConns(10) | |
fmt.Println("goUse 数据库连接成功")return | |
} | |
//CheckErr 异常捕获函数 | |
func CheckErr(err error) {if err != nil { | |
fmt.Println(err)panic(err)} | |
} | |
func main() { | |
err := InitDB()CheckErr(err) | |
} | |
sqlx相较于原生的sql库好处在于 查询的时候sql原生的需要next scan 回调获取结果
sqlx 查询只需要定义一个存储的变量 然后自动就会将查询的出来的值放入变量中
package main | |
import ("encoding/json""fmt" | |
_ "github.com/go-sql-driver/mysql""github.com/jmoiron/sqlx" | |
) | |
var db *sqlx.DB | |
type user struct { | |
ID int `json:"id"` | |
Username string `json:"username"` | |
Password string `json:"password"` | |
} | |
// InitDB 数据库初始化 | |
func InitDB() (err error) { | |
dsn := "root:1qa2ws3ed@tcp(127.0.0.1:3306)/gouse"// Connect 就是连接的同时db.ping()一下 | |
db, err = sqlx.Connect("mysql", dsn)CheckErr(err) | |
db.SetMaxOpenConns(50) | |
db.SetMaxIdleConns(10) | |
fmt.Println("goUse 数据库连接成功")return | |
} | |
// SelectDB 查询单条数据的方法 | |
func SelectDB() { | |
sqlStr := `select * from test where id=?`var data user | |
_ = db.Get(&data, sqlStr, 990)//CheckErr(err) | |
fmt.Printf("%#v\n", data) | |
marshal, err := json.Marshal(data)CheckErr(err) | |
fmt.Println(string(marshal)) | |
} | |
// ManySelect 查询多条数据方法 | |
func ManySelect() { | |
sqlStr := `select * from test where id < ?`var dataList []user | |
err := db.Select(&dataList, sqlStr, 1000)CheckErr(err)//fmt.Println(dataList) | |
marshal, err := json.Marshal(dataList)CheckErr(err) | |
fmt.Println(string(marshal)) | |
} | |
//CheckErr 异常捕获函数 | |
func CheckErr(err error) {if err != nil { | |
fmt.Println(err)panic(err)} | |
} | |
func main() { | |
err := InitDB()CheckErr(err)SelectDB()ManySelect() | |
} |
Go操作Redis
安装go get -u github.com/go-redis/redis
package main | |
import ("fmt" | |
"github.com/go-redis/redis" | |
) | |
var redisDB *redis.Client | |
// InitRedisDB redis数据库初始化 | |
func InitRedisDB() (err error) { | |
redisDB = redis.NewClient(&redis.Options{ | |
Addr: "127.0.0.1:6379", | |
Password: "", | |
DB: 0,})_, err = redisDB.Ping(redisDB.Context()).Result()CheckErr(err) | |
fmt.Println("redis 连接成功")return | |
} | |
//CheckErr 异常捕获函数 | |
func CheckErr(err error) {if err != nil { | |
fmt.Println(err)panic(err)} | |
} | |
func main() {_ = InitRedisDB() | |
} | |
set(key, value):给数据库中名称为key的string赋予值value | |
get(key):返回数据库中名称为key的string的value | |
getset(key, value):给名称为key的string赋予上一次的value | |
mget(key1, key2,…, key N):返回库中多个string的value | |
setnx(key, value):添加string,名称为key,值为value | |
setex(key, time, value):向库中添加string,设定过期时间time | |
mset(key N, value N):批量设置多个string的值 | |
msetnx(key N, value N):如果所有名称为key i的string都不存在 | |
incr(key):名称为key的string增1操作 | |
incrby(key, integer):名称为key的string增加integer | |
decr(key):名称为key的string减1操作 | |
decrby(key, integer):名称为key的string减少integer | |
append(key, value):名称为key的string的值附加value | |
substr(key, start, end):返回名称为key的string的value的子串 |
NSQ分布式消息队列
NSQ是目前比较流行的一个分布式消息队列,下面主要是NSQ及GO语言如何操作NSQ
NSQ是GO语言编写的一个开源的实时分布式内存消息队列, 其性能十分优异, NSQ的优势有:
1.NSQ提倡分布式和扩散的拓扑,没有单点故障,支持容错和高可用性,并提供可靠的消息交付保证
2.NSQ支持横向扩展, 没有任何集中式代理
3.NSQ易于配置和部署,并且内置了管理界面
安装go get -u github.com/nsqio/go-nsq
Context
在Go HTTP 包的server中,每一个请求都在对应着一个响应,请求处理函数通常会启动额外的goroutine用来访问后端的服务,比如数据库和rpc服务,用来处理一个请求的goroutine通常需要访问一些与请求特定的数据,比如终端的身份认证信息、验证相关的token、请求和截止时间。当一个请求被取消或超时时,所有用来处理该请求的goroutine都应该迅速退出,然后系统才能释放这些goroutine | |
如何优雅的结束goroutine释放资源
// 通道版本 | |
package main | |
import ("fmt""sync""time" | |
) | |
var wg sync.WaitGroup | |
func worker(exitChan <-chan struct{}) { | |
defer wg.Done() | |
Test:for { | |
fmt.Println("worker") | |
time.Sleep(time.Second) | |
select {case <-exitChan:break Test | |
default:} | |
} | |
} | |
func main() { | |
wg.Add(1) | |
c := make(chan struct{}) | |
go worker(c) | |
time.Sleep(10 * time.Second) | |
c <- struct{}{}close(c) | |
wg.Wait() | |
fmt.Println("Over") | |
} | |
// Context版本 | |
package main | |
import ("context""fmt""sync""time" | |
) | |
var wg sync.WaitGroup | |
func worker(ctx context.Context) { | |
defer wg.Done() | |
Test:for { | |
fmt.Println("worker") | |
time.Sleep(time.Second) | |
select {case <-ctx.Done():break Test | |
default:} | |
} | |
} | |
func main() { | |
wg.Add(1) | |
ctx, cancelFunc := context.WithCancel(context.Background()) | |
go worker(ctx) | |
time.Sleep(10 * time.Second) | |
cancelFunc() | |
wg.Wait() | |
fmt.Println("Over") | |
} |
如果goroutine开启了新的goroutine,只需要将ctx传入到新的goroutine中即可
Background() 和 TODO()
go内置两个函数: Background() 和TUDO(),这两个函数分别返回了一个实现了context接口的background和todo. 我们代码中最开始都是以这两个内置的上下文对象作为最顶层的partent context,衍生出更多的子上下文对象。
backgroud() 主要用于main函数,初始化以及代码测试,作为context这个树结构的最顶层context,也就是跟context。
todo(),他目前还不知道能干点啥?
使用context的注意事项
- 推荐以参数显示传递context
- 以context作为参数的函数方法,应该把context作为第一个参数
- 给一个函数传递context的时候,不要nil,如果不知道传递什么,就使用context.TODO()
- context是并发安全的,可以随意在多个goroutine中传递
log标准库
log包定义了Logger类型, 该类型提供了一些格式化输出的方法。本包也提供了一个预定义的标准logger,可以通过调用函数Print系列,fatal系列和panic系列来使用,比自行创建的logger对象更容易使用。
package main | |
import "log" | |
func main() { | |
log.Println("这是第一条工作日志") | |
v := "THIS is worker log" | |
log.Printf("%#v\n", v)// Fatal将会值写入信息之后,执行exit(1) | |
log.Fatal("之后写一万行代码 我也不执行了哦") | |
// 可以通过log.Panic 引发异常 会将日志写入之后引发异常 | |
log.Panic("测试panic的日志") | |
} |
- flag选项(日志输出内容设置)
log标准库提供了如下的flag选项,他们是一系列定义好的常量。 | |
const ( | |
Ldate = 1 << iota | |
Ltime | |
Lmicroseconds | |
Llongfile | |
Lshortfile | |
LUTC | |
LstdFlags = Ldate | Ltime | |
) | |
package main | |
import "log" | |
func main() {// 设置默认附加的内容 | |
log.SetFlags(log.Llongfile | log.Ltime)// 设置日志前缀 | |
log.SetPrefix("[go_log] ") | |
log.Println("测试日志") | |
} | |
output>>> | |
[go_log] 19:02:14 /Users/mac/GolandProjects/src/day02/go_log库/main.go:19: 测试日志 | |
- 配置日志输出位置
setoutput
函数用来设置logger的输出目的地,默认是标准错误输出
package main | |
import ("log""os" | |
) | |
func main() { | |
file, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)if err != nil { | |
log.Panic("文件打开失败")}// 设置了写入文件 日志内容就不会打印到终端了 | |
log.SetOutput(file) | |
log.SetFlags(log.Llongfile | log.Ltime) | |
log.SetPrefix("[go_log] ") | |
log.Println("测试日志") | |
} | |
我们可以定义一个init初始化函数 将log全部配置好 这样更加标准化 |
第三方日志库logrus的使用
logrus是GO结构化的logger 与上边的logger标准库完全兼容
安装logrusgo get github.com/sirupsen/logrus
package main | |
import ( | |
log "github.com/sirupsen/logrus" | |
) | |
func main() { | |
log.WithFields(log.Fields{"animals": "dog","time": log.FieldKeyTime,}).Info("这是啥") | |
} |
- 日志级别
Trace、debug、info、warning、error、fatal、panic
log.Trace("跟踪?") | |
log.Debug("Debug?") | |
log.Info("信息") | |
log.Warn("警告?") | |
log.Error("Something failed but I'm not quitting.")// 记完日志后会调用os.Exit(1) | |
log.Fatal("Bye.")// 记完日志后会调用 panic() | |
log.Panic("I'm bailing.") |
- 日志记录
package main | |
import ("os""time" | |
log "github.com/sirupsen/logrus" | |
) | |
func main() { | |
file, err := os.OpenFile("logrustest.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)if err != nil { | |
log.Panicln(err)} | |
log.SetOutput(file)for i := 0; i < 100; i++ { | |
log.WithFields(log.Fields{"animals": "dog","Countey": "China","City": "BeiJing",}).Info("这是啥") | |
time.Sleep(time.Second)} | |
log.Trace("跟踪?") | |
log.Info("信息") | |
log.Warn("警告?")// 设置日志级别, 会记录info以上级别(warn error fatal panic) | |
log.SetLevel(log.InfoLevel) | |
} | |
>>>结果 | |
time="2021-02-04T12:00:15+08:00" level=info msg="这是啥" City=BeiJing Countey=China animals=dog | |
time="2021-02-04T12:00:17+08:00" level=info msg="这是啥" City=BeiJing Countey=China animals=dog | |
time="2021-02-04T12:00:18+08:00" level=info msg="这是啥" City=BeiJing Countey=China animals=dog | |
time="2021-02-04T12:00:19+08:00" level=info msg="这是啥" City=BeiJing Countey=China animals=dog |
日志的条目除了使用withfield 和withfields添加的相关日志,还有一些默认添加的日志字段
time 记录日志的时间戳 msg 记录日志信息 level记录日志级别
- 日志格式化
logrus内置一下两种日志格式化程序
logrus.TextFormatter logrus.JSONFormatter
log.SetFormatter(&log.JSONFormatter{})
- 追踪函数
log.SetReportCaller(true) | |
这样就会将哪个文件哪一行 都记录下来 但是不是特殊需求无需开启这个 因为会增加性能开销 |