利用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]Tagfunc 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中能够指定,并且还能够反对多个表一起)
如果有哪儿存在问题的欢送指导