一开始开了 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
alex8 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。
|