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)
	这样就会将哪个文件哪一行 都记录下来  但是不是特殊需求无需开启这个 因为会增加性能开销