利用 go 实现 mysql 批量测试数据生成 -TDG
组件代码:https://gitee.com/dn-jinmin/tdg
下载go get gitee.com/dn-jinimin/tdg
目前只针对 mysql,只反对多个单表批量新增,关联新增还未实现实现
01. 登程
之所以开发 TDG 这个工具次要是因为,很多敌人在学习数据库索引及 SQL 优化、本人我的项目开发等状况的时候都会面临一个问题;就是没有数据能够测试;
比方 SQL 优化最显著,在目前零碎中数据量在 500w 以上及 1000w 的时候建设索引并尝试去进行优化,这样的成果比几条数据仅通过 explain 剖析更为直观,我也遇到过这个问题
测试数据的生成形式有很多种;比方利用数据库的存储过程,或者本人用程序写一个等等,因而就一不小心本人就写了一个,顺带再好好坚固 go;
利用 go 开发的,并且编译成了可执行文件能够间接下载使用;
舒适提醒:应用中请留神看代码文档配置,稍有不慎就 …;
__当然心愿这个工具可能给你带来好的帮忙,上面是对工具的实现介绍,及实现思路,集体能力无限如果你应用中有更好意见欢送指导,冀望能 star 一下 Thanks♪(・ω・)ノ
02. 构思
mysql 对于数据的写入
mysql 对于数据的写入惯例的形式就是执行如下 SQL 命令写入数据
insert into table_name (col1,col2)values(data1, data2)
如果存在一份 xxx.sql 的文件能够通过
source xxx.sql
还能够采纳 mysql 中的
load data xxx xxx.txt
针对 mysql 如何疾速批量生成测试数据须要解决的几个问题
- 如何疾速生成?
- 如何针对表生成
- 须要针对不同字段要求生成数据
- 可自定义对不同的表去生成
这是针对 mysql 如何高效的生成测试数据必须要思考并要解决的问题,我的思考过程 …(因为篇幅太长此处省略 ….N.. 字)
tdg 实现思路
首先在整体的思路上是基于 sql,通过 insert 的形式写入的策略实现的;利用 insert 语句批量新增的形式;如下格局
insert into table_name (col1,col2) values(data1, data2),(data1,data2)
tdg 对于 SQL 的生成分为三块
1. SQL 开始与 table_name
insert into table_name
2. 字段列
(col1, col2)
3. 写入数据
values(data1, data2),(data1, data2)
tdg 则就是针对 SQL 的生成与拼接组装最终构建一条批量新增的 insert 语句,将生成的数据写入到 mysql 中;
03. 具体实现
在整体的实现中能够分为四个节点:初始化、数据生成规定、SQL 生成、SQL 写入
数据生成规定
这里咱们先理解一下规定是如何实现的,因为思考到在 mysql 中对于字段咱们会因为数据的特点会有对应的要求,比方 id 能够是 int 也能够是 string、order 会有属于它本人的要求形式,phone,date,name 等等会存在多种类型的数据要求;
因而规定的设计次要是依据列的利用所对应的类型设计会蕴含如下的规定
这些规定会在初始化的时候加载;整体的设计上是定义一个对立的规定接口在 build/filed.go
中
type Column struct {
// 字段标签,确定字段生成形式
Tag string `json:"tag"`
// 字段固定长度
Len int `json:"len"`
// 长度范畴, 可变长度
FixedLen interface{} `json:"fixed_len"`
// 字段最大值
Max int `json:"max"`
// 字段最小值
Min int `json:"min"`
// 其它参数
Params interface{} `json:"params"`
// 默认值
Default struct {Value interface{} `json:"value"`
Frequency int `json:"frequency"`
} `json:"default"`
// 指定随机内容
Content []interface{} `json:"content"`
}
type Tag interface {Name() string
Handler(column *Column) string
Desc() string}
Name() 代表是规定的标签名称,Hander 具体的随机办法,Desc 打印输出的形容; 思考到规定的丰富性定义一个对立的 column 构造体作为 hander 的参数,而不同规定能够依据 column 中的参数设置进行随机生成想要的后果;如下为示例,
type Char struct{}
func (*Char) Name() string {return "char"}
func (*Char) Handler(column *Column) string {
var ret strings.Builder
chLen := column.Len
// 固定长度, 固定长度优先级大于可变长度
if column.FixedLen != nil {chLen = column.PrepareFixedLen()
}
ret.Grow(chLen)
// 可变长度
for i := 0; i < chLen; i++ {ret.WriteByte(chars[RUint(62)])
}
return ret.String()}
func (*Char) Desc() string {return "随机字符 0~9, a~z, A~Z"}
所有的规定对立定义在 build/tag.go
中;最终规定会加载到 build/field.go
中
type Field map[string]Tag
func NewField(cap int) Field {return make(map[string]Tag, cap)
}
func (field Field) Register(tag string, option Tag)
func (field Field) TagRandField(column *Column, sql *strings.Builder)
规定也能够自定义哟只须要实现下面的接口,而后再利用 filed 的 Register 办法注册就好了
初始化:
在 tdg 中存在 Table 构造体代表是某一个表
type Table struct {
TableName string `json:"table_name"`
Columns map[string]*Column `json:"columns"`
Debug bool `json:"debug"`
Count int `json:"count"`
Status int `json:"status"`
columns []string
sqlPrefix *[]byte}
加载 table.json 配置, 在配置中会定义对于 MySQL 的连贯、table 的表名、字段及整体 tdg 对数据生成的策略
{
"tdg": {
"data_one_init_max_memory": 104857600,
"build_sql_worker_num": 5,
"insert_sql_worker_num": 10,
"build_batches_count": 2000,
// ..
},
"mysql": {
"addr": "","user":"",
"pass" : "","charset":"",
"dbname": "","table_prefix":""
},
"tables": {
"member": {
"status": 0,
"table_name": "member",
"count": 10,
"columns": {
"username": {
"tag": "name",
"fixed_len": "6|14"
},
//..
}
},
"member_info": {
"table_name": "member_info",
"count": 1,
"status": 0,
"columns": {//..}
}
}
}
同时也会加载一些自定义的规定, 加载后的数据信息会存最终存在 build 中,通过 build 驱动
type Build struct {
oneInitMaxMemory int
insertSqlWorkerNum int
buildSqlWorkerNum int
buildBatchesCount int
mysqlDsn string
cfg *Config
tables map[string]*Table
field Field
db *gorm.DB
wg sync.WaitGroup
}
在初识的时候会对所有筹备新增的表把前缀 insert into table_name (col1,col2)values(
筹备好
sql 生成与写入
针对这两个流程,在实现上是采纳了并发的 worker 模式,构建多个 GetSql,insert 的协程一起协调实现工作
在 run 中
func (b *Build) Run() {b.buildSqlWorkerPool(sqlCh, buildTaskCh)
b.insertSqlWorkerPool(sqlCh, ctx)
for _, table := range b.tables {go func(table *Table, ctx *Context) {
// 数据表是否写入
if table.Status == -1 {return}
task := &buildTask{
table: table,
quantity: b.buildBatchesCount,
sqlPrefix: table.prepareColumn(),}
degree, feel := deg(table.Count, b.buildBatchesCount)
ctx.addDegree(degree) // 统计次数
for i := 0; i < degree; i++ {
// 最初一次的解决
if feel > 0 && i == degree -1 {task.quantity = feel}
buildTaskCh <- task
}
}(table, ctx)
}
}
在流程的设计上是会对每个表分工作的形式去生成,一次生成多少的 SQL 并进行写入;通过 deg 计算工作,而工作信息就是 buildTask 通过通道 buildTaskCh 发送工作
同时在 run 中还会创立好对应 insertSql 与 getSql 的工作者数量
在 getSql 中会依据标签调用不同的规定生成,随机的数值并进行 SQL 的拼接
func (b *Build) getSql(quantity int, sqlPrefix *[]byte,table *Table, sqlCh chan *strings.Builder) {
// ..
for i := 0; i < quantity; i++ {table.randColums(sql, b.field)
if i == quantity-1 {break}
sql.WriteByte(',')
}
sqlCh <- sql
}
在生成完 SQL 之后把后果发送到 sqlch 通道中;insertSql 则读取并利用 gorm 写入
func (b *Build) insert(sql *strings.Builder, ctx *Context) {
if !b.cfg.NoWrite {if err := b.db.Exec(sql.String()).Error; err != nil {
if b.cfg.Debug {ctx.errs = append(ctx.errs, err)
}
if !b.cfg.TDG.SkipErr {ctx.Cancel()
return
}
}
}
ctx.complete++
if ctx.complete == ctx.degree {ctx.Cancel()
}
}
根本实现如上
04. 总结
tdg 中对于如下问题的解决
- 如何疾速生成?(利用并发编程 worker 工作模式、构建多个 getSQL 与 insertSql 配合工作,细节能够看具体代码哟)
- 如何针对表生成(通过配置 table.json 指定,并且还须要指定生成的规定)
- 须要针对不同字段要求生成数据(通过定义规定的生成构造体及固定传参来进步生成规定的丰盛度)
- 可自定义对不同的表去生成(同样也是 table.json 中能够指定,并且还能够反对多个表一起)
如果有哪儿存在问题的欢送指导