Go 语言操作 MySQL 更新数据,使用事务以后耗时反而增加 30 倍

2017-07-21 11:22:29 +08:00
 billion

一开始开了 100 个 goroute,在每个 goroute 里面一条一条更新数据:

for i:=0; i<100;i++{
    go func(chan){
      para1 := <- chan
      stmt, _ := db.Prepare("update.....")
      stmt.Exec(para1)
    }
}

可以做到 2 秒钟更新 1000 条。

后来改用事务来批量更新

for i:=0; i<100;i++{
    go func(chan){
      var paraArray []string
      for para := range chan{
         paraArray = append(paraArray, para)
         if len(paraArray) >= 1000 {
             tx, _ := db.Begin()
             for _, para := range paraArray{
                  tx.Exec("update.....", para) 
             }
              tx.Commit()
             paraArray = paraArray[:0]
          }
        }
    }
}

这样每个事务里面的 1000 条语句,运行时间高达 1 分钟。请问为什么用事务反而导致效率严重降低了?

3683 次点击
所在节点    Go 编程语言
49 条回复
elgae
2017-07-21 13:20:06 +08:00
循环内开事务,不对吧。
cloudzhou
2017-07-21 13:31:09 +08:00
@billion
```
stmt, _ := db.Prepare("update.....")
for i:=0; i<100;i++{
go func(chan, stmt){
para1 := <- chan
stmt.Exec(para1)
}
}
stmt 本身就是并发安全的,你改成这样试试看,效率如何
nadoo
2017-07-21 13:57:55 +08:00
后面的代码有点难懂,直观地看起来 2 个 for 循环,还都在从一个 chan 读数据?代码都不清晰的话,可能问题就很多了
billion
2017-07-21 15:18:47 +08:00
@nadoo 这个 chan 你可以理解为 Python 的 Queue。本来就应该是从 chan 里面读数据。
msg7086
2017-07-21 15:19:35 +08:00
你是不是应该先说说是 Go 拖慢了还是 MySQL 拖慢了?
首先网络是本地网,那么传输肯定是瞬时完成的。
你打开 htop / iotop 之类的看一下,你这 1 分钟里是谁在吃 CPU,谁在吃磁盘 IO,谁在卡。

从上到下这回复我看得一愣一愣的,23 层楼了还没确定是 MySQL 卡了还是 Go 程序卡了。
msg7086
2017-07-21 15:20:54 +08:00
然后如果是 Go 卡了,这我就不管了,不懂 Go。
如果是 MySQL 卡,一个是看看 Process List,啥语句卡,卡在哪步。
另一个是看一下 MySQL 的 statistics,看看有没有异常数值出现。
khowarizmi
2017-07-21 15:32:03 +08:00
补充楼上,如果是 Golang 的问题,用 pprof 查一下。
https://golang.org/pkg/net/http/pprof/
billion
2017-07-21 15:35:14 +08:00
@msg7086 是远程的 MySQL
billion
2017-07-21 15:36:45 +08:00
@cloudzhou 我现在就是这样用的,速度大概 1-2 秒 1000 条记录
nadoo
2017-07-21 16:09:51 +08:00
@billion 后面的代码,100 个 goroutine 同时等待同一个 chan,如果每个 goroutine 速度差不多,要等到这 100 个 goroutine 内都满了 1000 个 param 才开始提交任务。也就是 2 个条件:1.chan 内数据填满 100*1000=100000,2. 填满后,100 个 goroutine 同时提交事务,每个事务中有 1000 条语句。

感觉两段代码没什么可比性,真的要用 chan 的话,也应该是多个地方往 chan 里面写,然后只有一个地方读取 chan 并更新数据库,满 1000 条提交一次事务。
sun1991
2017-07-21 16:25:03 +08:00
各种数据库机制不同, MySQL 推荐做法是长事务还是短事物? 1000 个 update 提交一次会不会升级为表级锁?
billion
2017-07-21 16:33:34 +08:00
@nadoo 你这个想法很有意思。我去试一试。
billion
2017-07-21 16:35:42 +08:00
@nadoo 问题是,如果几个地方往 chan 写的速度很快。复杂提交事务的那个地方正在提交的过程中,此时另外 1000 个又来了,那就只有等待。
jarlyyn
2017-07-21 16:38:39 +08:00
@billion

你需要一个函数。两个 chan
比如
chan record
chan record[]
这个函数的作用是从单条记录的 chan recrod 里读数据,拼够数据后,写入 chan record[]
操作的时候记得加锁
操作数据库的协程读 chan record[],再写入。
billion
2017-07-21 16:54:33 +08:00
@jarlyyn 正式代码里面就是这样的。这个帖子的代码经过精简。协程没有必要加锁吧,协程又不会发送读写冲突。
kurtzhong
2017-07-21 17:08:04 +08:00
开一百个线程有什么意义?
jarlyyn
2017-07-21 17:13:34 +08:00
@billion

你需要把 chan record 的记录一条条读出来,写入一个临时的 record[]

临时的 record[]写满后,再写入 chan record[]
specita
2017-07-21 17:24:23 +08:00
@billion 用的 go,我认为你慢的原因不在于事务,而是你的这段测试代码写的真的有问题..,上面也有人说,让你先确定是 mysql 问题还是代码执行的问题
nadoo
2017-07-21 18:08:15 +08:00
@billion 如果数据库就是慢,那是没办法的。就只有使用缓冲 channel,排队更新数据库,这样写 chan 的地方也不会阻塞,待并发时段过去了,就好了。
amghost
2017-07-21 18:10:27 +08:00
为什么会扯到 mysql 去我也是不懂。各位看看这两段代码,假设所有 goroutine 获取到的 para 数量评论,显然在第二个案例里面的一个 goroutine 跑完 1000 个的时候整个系统已经提供 100x1000 个 para,这里 chan<-的速率是怎样的?数据是不是有重叠的?如果有重叠那事务之间就互斥了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/376931

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX