数据库中的重型字段迁移优化(golang)

·

1 min read

最近在数据迁移中的一些优化点,在这里分享一些,当然也不一定是最优的,欢迎讨论。

首先,在某些数据表中的字段,存着非常大的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 .... } } }()

这样就比较满意了,但是这种方式还没有经过测试,理论上读写分离之后,性能是更优的。