一开始开了 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 分钟。请问为什么用事务反而导致效率严重降低了?
![]() | 1 sagaxu 2017-07-21 11:25:05 +08:00 你 paraArray 满 1000 个插入后,什么时候清空? |
![]() | 3 ipconfiger 2017-07-21 11:26:36 +08:00 事务就是批量????????? |
![]() | 4 fqzz 2017-07-21 11:27:01 +08:00 难道事务还能让 update 更快? |
![]() | 5 billion OP @fqzz 根据网友的实际测试,正常情况下把很多条语句放到事务里面一次性 Commit,会快非常多,特别是对于减少网络 IO 的时间消耗很有用。我这个应该是不知道哪里不对。 |
![]() | 9 billion OP @sagaxu 应该是事务的问题。如果我把事务里面只执行 1 条语句,那么速度又可以达到大概 5 秒 1000 条。虽然还是没有直接执行快,但是已经显著超过在事务里面执行 1000 条。 |
![]() | 10 jarlyyn 2017-07-21 11:39:11 +08:00 这代码把我看的一愣一愣的。 发现最近愣的比较多…… 发现楼主连 go 这么玩不出花的语言都能玩的这么神奇,不做产品可惜了。 |
11 mansur 2017-07-21 11:41:03 +08:00 go 不太熟。range chan 遍历后消费掉了吗?没有消费掉岂不是每个协程都跑一遍。你第一种写法可是消费掉的。 |
![]() | 12 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 的不就不了。 |
![]() | 15 billion OP @jarlyyn 代码 1 更新 1000 条记录,用时 2 秒左右。代码 2 每个协程都需要大概 1 分钟才能更新 1000 条记录。 |
![]() | 16 jarlyyn 2017-07-21 12:02:47 +08:00 @billion 好吧,我大概明白了。 你这代码肯定和线上的不太一样。 但这代码问题也实在太大了。 现在的问题就是,1000 条记录,一次 commit,时间长的夸张对吗? 按道理来说,开事务是没道理比不开事务快的,但是慢这么多也是不符合常理的。 |
![]() | 17 specita 2017-07-21 12:04:19 +08:00 你这个测试代码感觉没对,我自己简单测了下 one by one spend: 364.546674ms transaction spend: 295.024446ms 事务应该是更快的 |
![]() | 18 jarlyyn 2017-07-21 12:06:00 +08:00 由于不知道你的服务器软硬件情况,能否跑个不带协程不带条数判断的单纯写入测试? 从我的角度看,你的代码应该是这样的: 有个全局 slice,操作带锁。 每满一千条,推到 chan 里。 读 chan 写入数据库的可以是单协程,可以是多协程。一次写 1000 条。 |
![]() | 19 billion OP @specita 你也是用的 GO 语言吗?用的 MySQL driver 是 github.com/go-sql-driver/mysql 吗? |
![]() | 20 pubby 2017-07-21 12:52:04 +08:00 via Android 更新的字段有索引吗,有的话这样并发死锁几率应该挺高的 |
21 elgae 2017-07-21 13:20:06 +08:00 via iPhone 循环内开事务,不对吧。 |
![]() | 22 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 本身就是并发安全的,你改成这样试试看,效率如何 |
23 nadoo 2017-07-21 13:57:55 +08:00 后面的代码有点难懂,直观地看起来 2 个 for 循环,还都在从一个 chan 读数据?代码都不清晰的话,可能问题就很多了 |
![]() | 25 msg7086 2017-07-21 15:19:35 +08:00 你是不是应该先说说是 Go 拖慢了还是 MySQL 拖慢了? 首先网络是本地网,那么传输肯定是瞬时完成的。 你打开 htop / iotop 之类的看一下,你这 1 分钟里是谁在吃 CPU,谁在吃磁盘 IO,谁在卡。 从上到下这回复我看得一愣一愣的,23 层楼了还没确定是 MySQL 卡了还是 Go 程序卡了。 |
![]() | 26 msg7086 2017-07-21 15:20:54 +08:00 然后如果是 Go 卡了,这我就不管了,不懂 Go。 如果是 MySQL 卡,一个是看看 Process List,啥语句卡,卡在哪步。 另一个是看一下 MySQL 的 statistics,看看有没有异常数值出现。 |
![]() | 27 khowarizmi 2017-07-21 15:32:03 +08:00 补充楼上,如果是 Golang 的问题,用 pprof 查一下。 https://golang.org/pkg/net/http/pprof/ |
30 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 条提交一次事务。 |
![]() | 31 sun1991 2017-07-21 16:25:03 +08:00 各种数据库机制不同, MySQL 推荐做法是长事务还是短事物? 1000 个 update 提交一次会不会升级为表级锁? |
![]() | 33 billion OP @nadoo 问题是,如果几个地方往 chan 写的速度很快。复杂提交事务的那个地方正在提交的过程中,此时另外 1000 个又来了,那就只有等待。 |
![]() | 34 jarlyyn 2017-07-21 16:38:39 +08:00 @billion 你需要一个函数。两个 chan 比如 chan record chan record[] 这个函数的作用是从单条记录的 chan recrod 里读数据,拼够数据后,写入 chan record[] 操作的时候记得加锁 操作数据库的协程读 chan record[],再写入。 |
36 kurtzhong 2017-07-21 17:08:04 +08:00 开一百个线程有什么意义? |
![]() | 37 jarlyyn 2017-07-21 17:13:34 +08:00 |
![]() | 38 specita 2017-07-21 17:24:23 +08:00 @billion 用的 go,我认为你慢的原因不在于事务,而是你的这段测试代码写的真的有问题..,上面也有人说,让你先确定是 mysql 问题还是代码执行的问题 |
39 nadoo 2017-07-21 18:08:15 +08:00 @billion 如果数据库就是慢,那是没办法的。就只有使用缓冲 channel,排队更新数据库,这样写 chan 的地方也不会阻塞,待并发时段过去了,就好了。 |
![]() | 40 amghost 2017-07-21 18:10:27 +08:00 via iPhone 为什么会扯到 mysql 去我也是不懂。各位看看这两段代码,假设所有 goroutine 获取到的 para 数量评论,显然在第二个案例里面的一个 goroutine 跑完 1000 个的时候整个系统已经提供 100x1000 个 para,这里 chan<-的速率是怎样的?数据是不是有重叠的?如果有重叠那事务之间就互斥了 |
43 xaxb 2017-07-21 23:18:16 +08:00 mysql 默认单数据更新语句自动事物,你只发了一条 updae....,Mysql 会自动用 Begin 和 Commit 把你的语句事物化。单个语句加显式事物没什么意义 |
![]() | 44 akira 2017-07-21 23:49:30 +08:00 1. 看看你的 update 是不是导致表锁了. 2. 多条语句合并到一个事务内提交以提高效率 比较适合 用于单会话 导数据 3. 高并发下,事务越短越好。 |
![]() | 45 wdlth 2017-07-22 00:16:18 +08:00 LZ 的 MySQL 用的是 InnoDB 还是 MyISAM 存储引擎? |
![]() | 48 abcbuzhiming 2017-07-22 10:14:20 +08:00 @wdlth MyISAM 没有事务,能开事务必然是 InnoDB |
![]() | 49 wdlth 2017-07-22 18:14:40 +08:00 @abcbuzhiming MariaDB 的 Aria 也能开事务,不过他没明确说是 MariaDB,那应该还是 InnoDB。 |