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 分钟。请问为什么用事务反而导致效率严重降低了?

3639 次点击
所在节点    Go 编程语言
49 条回复
sagaxu
2017-07-21 11:25:05 +08:00
你 paraArray 满 1000 个插入后,什么时候清空?
billion
2017-07-21 11:25:45 +08:00
@sagaxu 插完以后就清空,Commit 后面,代码已经修改
ipconfiger
2017-07-21 11:26:36 +08:00
事务就是批量?????????
fqzz
2017-07-21 11:27:01 +08:00
难道事务还能让 update 更快?
billion
2017-07-21 11:28:30 +08:00
@fqzz 根据网友的实际测试,正常情况下把很多条语句放到事务里面一次性 Commit,会快非常多,特别是对于减少网络 IO 的时间消耗很有用。我这个应该是不知道哪里不对。
sagaxu
2017-07-21 11:29:03 +08:00
@billion 改成只用一个 goroutine 看看时间是否有变化
billion
2017-07-21 11:31:54 +08:00
@sagaxu 我也测试过了。依旧非常满。
sagaxu
2017-07-21 11:33:56 +08:00
@billion 如果 1 个 routine 跟 100 个 routine 性能一样了,说明这是伪并发了
billion
2017-07-21 11:36:18 +08:00
@sagaxu 应该是事务的问题。如果我把事务里面只执行 1 条语句,那么速度又可以达到大概 5 秒 1000 条。虽然还是没有直接执行快,但是已经显著超过在事务里面执行 1000 条。
jarlyyn
2017-07-21 11:39:11 +08:00
这代码把我看的一愣一愣的。

发现最近愣的比较多……

发现楼主连 go 这么玩不出花的语言都能玩的这么神奇,不做产品可惜了。
mansur
2017-07-21 11:41:03 +08:00
go 不太熟。range chan 遍历后消费掉了吗?没有消费掉岂不是每个协程都跑一遍。你第一种写法可是消费掉的。
jarlyyn
2017-07-21 11:48:22 +08:00
吐槽完了具体说说。

说先,不知道为啥要开协程…………只是为了给 mysql 做压力测试么……

如果我没理解错的话,是 100 个协程同时链接 Mysql,然后还开事务,互相锁表竞争?感觉这个是最容易出问题的地方

更何况,如果 Chan 长度是 1000 的话,代码 1 更新了 1000 条记录

代码 2 更新了 1000×100 条记录

其次。代码 1prepare 了,代码 2 没有。虽然我不知道大妈 1 为啥每次都要 preapre

第三。每次都 append 一下干嘛。虽然对效率影响微乎其微,但既然是长度固定是 1000,直接 make 个 1000 的不就不了。
billion
2017-07-21 11:49:14 +08:00
@mansur 消费掉了的。channel 相当于 Python 里面的 queue,用一个少一个。
billion
2017-07-21 11:50:47 +08:00
@jarlyyn 因为在生产环境里面的代码是分布在多个函数里面的,我这里把它们放在了一起。
billion
2017-07-21 11:52:37 +08:00
@jarlyyn 代码 1 更新 1000 条记录,用时 2 秒左右。代码 2 每个协程都需要大概 1 分钟才能更新 1000 条记录。
jarlyyn
2017-07-21 12:02:47 +08:00
@billion

好吧,我大概明白了。

你这代码肯定和线上的不太一样。

但这代码问题也实在太大了。

现在的问题就是,1000 条记录,一次 commit,时间长的夸张对吗?

按道理来说,开事务是没道理比不开事务快的,但是慢这么多也是不符合常理的。
specita
2017-07-21 12:04:19 +08:00
你这个测试代码感觉没对,我自己简单测了下
one by one spend: 364.546674ms
transaction spend: 295.024446ms

事务应该是更快的
jarlyyn
2017-07-21 12:06:00 +08:00
由于不知道你的服务器软硬件情况,能否跑个不带协程不带条数判断的单纯写入测试?

从我的角度看,你的代码应该是这样的:

有个全局 slice,操作带锁。
每满一千条,推到 chan 里。

读 chan 写入数据库的可以是单协程,可以是多协程。一次写 1000 条。
billion
2017-07-21 12:33:10 +08:00
@specita 你也是用的 GO 语言吗?用的 MySQL driver 是 github.com/go-sql-driver/mysql 吗?
pubby
2017-07-21 12:52:04 +08:00
更新的字段有索引吗,有的话这样并发死锁几率应该挺高的

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

https://www.v2ex.com/t/376931

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

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

© 2021 V2EX