数据库中的重型字段迁移优化(golang)
最近在数据迁移中的一些优化点,在这里分享一些,当然也不一定是最优的,欢迎讨论。
首先,在某些数据表中的字段,存着非常大的json数据。
并且有几十万行数据,需要更改json中的某个数组字段,追加一些元素。
常规方式
- 读取所有的行
- json反序列化
- 修改数据
- json序列号并保持到数据库
有几个问题
- 内存占用非常大,每行大概至少有8KB的数据,各种对象,内存申请,这个迁移程序可能会吃到数G的内存。
- 性能也不好,在读取数据库期间,其实是浪费了的
第二次优化
按每个团队划分,分别查询,内存降低了一点,因为golang中,使用过的对象并不会立即GC掉。
由于频繁的make对象,所以内存不会立即gc,内存还是比较高的。
第三次优化
使用golang中的Pool功能来复用对象:
var issueTypePool = sync.Pool{ New: func() interface{} { return make([]*IssueType, 0) }, }
数据库查询
issueTypes := issueTypePool.Get().([]*IssueType) _, err := tx.Select(&issueTypes, sql, teamUUID) if err != nil { return nil, err }
恢复对象到对象池:
defer issueTypePool.Put(issueTypes[:0])
这样已经不错了,对象能复用,内存占用取决于最大的团队的内存占用。。
第四次优化
使用golang中的游标方式。
下面的代码中有第二个参数就是channel缓冲区,并且在rows中边读边丢入channel
func AllObjects(tx sq.DB, issueTypeChan chan<- IssueType) error { // 。。。。 for rows.Next() { it := new(IssueType) err = rows.Scan(&it.UUID, &it.TeamUUID, &it.DefaultConfigs) if err != nil { return err } issueTypeChan <- it } return nil }
启动goroutine,并在持续从chan缓冲区读取数据,同时对数据修改迁移,并写入数据库。
issueTypeChan := make(chan IssueType, 1000) doneChan := make(chan bool) timer := time.NewTimer(5 time.Second) defer timer.Stop()
go func() { for { select { case <-timer.C: close(doneChan) fmt.Println("done...") return case it := <-issueTypeChan: timer.Reset(5 * time.Second) // TODO .... } } }()
这样就比较满意了,但是这种方式还没有经过测试,理论上读写分离之后,性能是更优的。