/
mysql.go
351 lines (294 loc) · 9.69 KB
/
mysql.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/** Package mysql of gorm library.
* gorm mysql封装,支持多个数据库实例化为连接池对象
* 结合了xorm思想,将每个数据库对象作为一个数据库引擎句柄
* xorm设计思想:在xorm里面,可以同时存在多个Orm引擎
* 一个Orm引擎称为Engine,一个Engine一般只对应一个数据库
* 因此,可以将gorm的每个数据库连接句柄,可以作为一个Engine来进行处理
* 容易踩坑的地方:
* 对于golang的官方sql引擎,sql.open并非立即连接db,用的时候才会真正的建立连接
* 但是gorm.Open在设置完db对象后,还发送了一个Ping操作,判断连接是否连接上去
* 对于短连接的话,建议用完就调用db.Close()方法释放db连接资源
* 对于长连接服务,一般建议在main/init中关闭连接就可以
* 具体可以看gorm/main.go源码85行
* 对于gorm实现读写分离:
* 可以实例化master,slaves实例,对于curd用不同的句柄就可以
* 由于gorm自己对mysql做了一次包裹,所以重命名处理
* gMysql "gorm.io/driver/mysql"
* gorm v2版本仓库地址:https://github.com/go-gorm/gorm
*/
package mysql
import (
"database/sql"
"errors"
"fmt"
"log"
"time"
"github.com/go-sql-driver/mysql"
gMysql "gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// engineMap 每个数据库连接pool就是一个db引擎
var engineMap = map[string]*gorm.DB{}
// DbConf mysql连接信息
// parseTime=true changes the output type of DATE and DATETIME
// values to time.Time instead of []byte / string
// The date or datetime like 0000-00-00 00:00:00 is converted
// into zero value of time.Time.
type DbConf struct {
Ip string
Port int // 默认3306
User string
Password string
Database string
Charset string // 字符集 utf8mb4 支持表情符号
Collation string // 整理字符集 utf8mb4_unicode_ci
UsePool bool // 当前db实例是否采用db连接池,默认不采用,如采用请求配置该参数
MaxIdleConns int // 空闲pool个数
MaxOpenConns int // 最大open connection个数
// sets the maximum amount of time a connection may be reused.
// 设置连接可以重用的最大时间
// 给db设置一个超时时间,时间小于数据库的超时时间
MaxLifetime int64 // 数据库超时时间,单位s
// 连接超时/读取超时/写入超时设置
Timeout time.Duration // Dial timeout
ReadTimeout time.Duration // I/O read timeout
WriteTimeout time.Duration // I/O write timeout
ParseTime bool // 格式化时间类型
Loc string // 时区字符串 Local,PRC
engineName string // 当前数据库连接句柄标识
dbObj *gorm.DB // 当前数据库连接句柄
hasInit bool // 是否调用了 InitInstance()进行初始化db
ShowSql bool // sql语句是否输出
// sql输出logger句柄接口
// logger.Writer 接口需要实现Printf(string, ...interface{}) 方法
// 具体可以看gorm v2 logger包源码
// https://github.com/go-gorm/gorm
Logger logger.Writer
// gorm v2版本新增参数
gMysqlConfig gMysql.Config // gorm v2新增参数gMysql.Config
gormConfig gorm.Config // gorm v2新增参数gorm.Config
LoggerConfig logger.Config // gorm v2新增参数logger.Config
}
// SetEngineName 给当前数据库指定engineName
func (conf *DbConf) SetEngineName(name string) error {
if name == "" {
return errors.New("current engine name is empty")
}
if !conf.hasInit {
return errors.New("current " + name + " db engine must be InitInstance")
}
conf.engineName = name
engineMap[conf.engineName] = conf.dbObj
return nil
}
// SetDbPool 设置db pool连接池
func (conf *DbConf) SetDbPool() error {
conf.UsePool = true
return conf.InitInstance()
}
// ShortConnect 建立短连接,用完需要调用Close()进行关闭连接,释放资源
// 否则就会出现too many connection
func (conf *DbConf) ShortConnect() error {
conf.UsePool = false
return conf.InitInstance()
}
// Close 关闭当前数据库连接
// 一般建议,将当前db engine close函数放在main/init关闭连接就可以
func (conf *DbConf) Close() error {
if conf.dbObj == nil {
return nil
}
db, err := conf.SqlDB()
if err != nil {
log.Println("get db instance error: ", err.Error())
return err
}
err = db.Close()
if err != nil {
log.Println("close db instance error: ", err.Error())
return err
}
if conf.engineName != "" {
// 把连接句柄对象从map中删除
delete(engineMap, conf.engineName)
}
return nil
}
// Db 返回当前db对象
func (conf *DbConf) Db() *gorm.DB {
return conf.dbObj
}
// SqlDB 返回sql DB
func (conf *DbConf) SqlDB() (*sql.DB, error) {
return conf.dbObj.DB()
}
// DSN 设置mysql dsn
// mysql charset查看
// mysql> show character set where charset="utf8mb4";
// +---------+---------------+--------------------+--------+
// | Charset | Description | Default collation | Maxlen |
// +---------+---------------+--------------------+--------+
// | utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
// +---------+---------------+--------------------+--------+
// 1 row in set (0.00 sec)
func (conf *DbConf) DSN() (string, error) {
if conf.Ip == "" {
conf.Ip = "127.0.0.1"
}
if conf.Port == 0 {
conf.Port = 3306
}
if conf.Charset == "" {
conf.Charset = "utf8mb4"
}
// 默认字符序,定义了字符的比较规则
if conf.Collation == "" {
conf.Collation = "utf8mb4_general_ci"
}
if conf.Loc == "" {
conf.Loc = "Local"
}
if conf.Timeout == 0 {
conf.Timeout = 10 * time.Second
}
if conf.WriteTimeout == 0 {
conf.WriteTimeout = 5 * time.Second
}
if conf.ReadTimeout == 0 {
conf.ReadTimeout = 5 * time.Second
}
// mysql connection time loc.
loc, err := time.LoadLocation(conf.Loc)
if err != nil {
return "", err
}
// mysql config
mysqlConf := mysql.Config{
User: conf.User,
Passwd: conf.Password,
Net: "tcp",
Addr: fmt.Sprintf("%s:%d", conf.Ip, conf.Port),
DBName: conf.Database,
// Connection parameters
Params: map[string]string{
"charset": conf.Charset,
},
Collation: conf.Collation,
Loc: loc, // Location for time.Time values
Timeout: conf.Timeout, // Dial timeout
ReadTimeout: conf.ReadTimeout, // I/O read timeout
WriteTimeout: conf.WriteTimeout, // I/O write timeout
AllowNativePasswords: true, // Allows the native password authentication method
ParseTime: conf.ParseTime, // Parse time values to time.Time
}
return mysqlConf.FormatDSN(), nil
}
// InitInstance 建立db连接句柄
// 创建当前数据库db对象,并非连接,在使用的时候才会真正建立db连接
// 为兼容之前的版本,这里新增SetDb创建db对象
func (conf *DbConf) InitInstance() error {
if conf.hasInit {
return nil
}
// sql日志级别
if conf.LoggerConfig.LogLevel == 0 {
conf.LoggerConfig.LogLevel = logger.Info
}
// 是否输出sql日志
// 这里重写了之前的gorm v1版本的日志输出模式
if conf.ShowSql {
// 日志对象接口
var dbLogger logger.Interface
if conf.Logger == nil {
dbLogger = logger.Default
} else {
dbLogger = logger.New(conf.Logger, conf.LoggerConfig)
}
// 设置gorm logger句柄对象
conf.gormConfig.Logger = dbLogger
} else {
conf.gormConfig.Logger = logger.Discard // 默认是不输出sql
}
var err error
if conf.gMysqlConfig.DSN == "" {
dsn, err := conf.DSN()
if err != nil {
log.Println("mysql dsn format error: ", err)
return err
}
conf.gMysqlConfig.DSN = dsn
}
// 下面这种方式实例的gorm.DB 很多参数都没法正确设置,不推荐这么实例化
// conf.dbObj, err = gorm.Open(gMysql.Open(conf.gMysqlConfig.DSN), &gorm.Config{
// Logger: conf.gormConfig.Logger,
// })
// 对于golang的官方sql引擎,sql.open并非立即连接db,用的时候才会真正的建立连接
// 但是gorm.Open在设置完db对象后,还发送了一个Ping操作,判断连接是否连接上去
// 具体可以看gorm/main.go源码Open方法
conf.dbObj, err = gorm.Open(gMysql.New(conf.gMysqlConfig), &conf.gormConfig)
if err != nil {
log.Println("open mysql connection error: ", err)
return err
}
// 设置连接池
var sqlDB *sql.DB
if !conf.hasInit {
sqlDB, err = conf.SqlDB()
if err != nil {
log.Println("get sql db error: ", err)
return err
}
}
if conf.UsePool {
sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
}
// 设置连接可以重用的最大存活时间,时间小于数据库的超时时间
if conf.MaxLifetime > 0 {
sqlDB.SetConnMaxLifetime(time.Duration(conf.MaxLifetime) * time.Second)
}
conf.hasInit = true
return nil
}
// ========================辅助函数===============
// GetDbObj 从db pool获取一个数据库连接句柄
// 根据数据库连接句柄name获取指定的连接句柄
func GetDbObj(name string) (*gorm.DB, error) {
if _, ok := engineMap[name]; ok {
return engineMap[name], nil
}
return nil, errors.New("get db obj failed")
}
// CloseAllDb 由于gorm db.Close()是关闭当前连接
// 一般建议如下函数放在main/init关闭连接就可以
func CloseAllDb() {
for name, db := range engineMap {
sqlDB, err := db.DB()
if err != nil {
log.Println("get db instance error: ", err.Error())
continue
}
err = sqlDB.Close()
if err != nil {
log.Println("close current db error: ", err)
continue
}
delete(engineMap, name) // 销毁连接句柄标识
}
}
// CloseDbByName 关闭指定name的db engine
func CloseDbByName(name string) error {
if db, ok := engineMap[name]; ok {
sqlDB, err := db.DB()
if err != nil {
return err
}
err = sqlDB.Close()
if err != nil {
return err
}
delete(engineMap, name) // 销毁连接句柄标识
}
return errors.New("current dbObj not exist")
}