Golang Go语言中 MySQL Update 200 万条数据的正确姿势是什么?

发布于 1周前 作者 songsunli 来自 Go语言

根据主键来更新每一条数据。

stmt, err := db.Prepare("update xxx set age=? where primaryid = ?")
panic(err)
_, err2 := stmt.Exec(age, id)

由于主键是唯一的,所以需要一条一条的更新。这种情况下,开了 2000 个 goroute 速度还是非常慢。请问有什么好办法吗?


Golang Go语言中 MySQL Update 200 万条数据的正确姿势是什么?

更多关于Golang Go语言中 MySQL Update 200 万条数据的正确姿势是什么?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

47 回复

大表更新一般选择建新表然后插入合并后的新数据,然后替换老表的方式比较快

更多关于Golang Go语言中 MySQL Update 200 万条数据的正确姿势是什么?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


一次拼 N 个 update 语句,一起发给 mysql 试试.

说实话,这个取决与 mysql 的效率。是否用 go 对效率提升作用不大。

可以批量处理的,比方说一次 1 千条数据。
另一个性能上的共识是 INSERT … ON DUPLICATE KEY UPDATE 速度快过 UPDATE,可以试试

这个用 goroutine 只会更慢啊,更新主键是有锁的 (gap lock), 你应该把 autocommit 关掉,每个 transaction 更新 1000 条,再 commit, 才 200w, 很快的

如果 age 上没索引,一会就跑完了

另一个点,如果你的表是 InnoDB,那么更新会锁表,Go 这边的并行处理不会直接提升速度

恩,这个可以试一试

我只想说这种简单的 update 语句 mysql 在 32 核上的性能都是上万 qps 了,区区 200w 数据一个小时内搞定吧

开一个事务做

这种操作协程 /线程开的越多越慢

Go 语言拼接 Update 的语句有点麻烦。

Go 语言拼接 Update 有点麻烦

用 Python 确实可以。但是 GO 语言没有 commit。。。。。

上事务会快一点, 如果是一次性操作
导出 csv,用 sed 或者程序改,再导入,这样时间可控

有 commit 仔细看文档

commit 是 MySQL 的概念,跟 go 有什么关系……

你的理解并不对,Innodb 主键更新并不会锁表,innodb 本身就是行锁; insert … on duplicate 比 update 快的依据是什么?怎么看也不会比 update 快啊,而且 insert on duplicate 这种语句如果真实执行是 update 的话在 binlog 中记录的也是 update 语句,实际执行的也是 update。


commit 肯定在 go 有对应的封装

随手搜索了一下(关键词: go commit mysql ),看上去里面有你要的
http://studygolang.com/articles/3022

这正是我想要的东西,非常感谢你的回答。

有更具体的操作方法不

生成 csv 然后用 MySQL 的 load data infile 功能
使用这个是单个事务且所有数据导入后才重建索引,比其他方法快很多。
注意处理唯一键冲突,选择 ignore 或者 replace



觉得 go 拼接 sql 语句麻烦是因为你不知道 sqlx 吧?

用 set case when 进行批量更新

上面那个关于 innoDB 的言论说反了

拼接语句,一次生成 1000 条语句在执行

用个 Golang ORM,告别手拼比如 jinzhu 的 gorm

我说的锁表概念是错的
关于 insert … key update 快是看情况的,处理单条数据 update 当然快,但是 insert … key update 可以放多条记录在一个 SQL 里的。根据 LZ 的情况,一次处理 1 千条数据,应该是比处理一千条 update 快。除非 age 是同一个值。

UPDATE a
SET fruit = (CASE id WHEN 1 THEN ‘apple’
WHEN 2 THEN ‘orange’
WHEN 3 THEN ‘peach’
END)
WHERE id IN(1,2 ,3);

很多 orm 支持这种批量 update,不需要用 insert

这种我没试过,回头验证一下。

因为一行 insert on duplicate 可以一次更新一千条记录,每条记录更新为不同值。

而用 update set 要更新不同值,只能写 1000 条语句。

关了 auto commit 一个 query 还是一个 round trip,还是 batch 快,比如 32l 的

上百个 id 这个 sql 得有多长

越长越快啊,没毛病。大量短语句才会变慢。

这里 INSERT ON DUPLICATE 其实比较好,因为只更新一两个值,SQL 语句并不会很臃肿。

这和用什么语言没太大关系,主要是这种提交方式的问题,用事务会快很多。

我 26 楼就说了,这方法批量更新很快

我昨天特地在我 16 年的 MacBook Air 128G SSD 上测试,200 万条简单数据的情况下(单条 SQL 更新 2000 条数据),60 秒左右更新完(用 PHP 7.0 PDO mysqli 驱动)
预期测试
age 有(无)索引,有(无)事务
单条更新(不打算测试,肯定慢得一逼)
set case when 批量更新(已测无索引,无事务)
insert on dup

对了,按理说,楼主举的这例子,先根据 new_age (在程序或 sql 中)进行 group,然后用
update set age=new_age where id in (1, 2…)
更新应该是最快的

跟语言没多大关系,2 个方法:
1、拼 SQL 批量执行;
2、批量事务:
START
UPDATE xxx SET age=? WHERE primaryid = ?
UPDATE xxx SET age=? WHERE primaryid = ?
UPDATE xxx SET age=? WHERE primaryid = ?

COMMIT

INSERT ON DUPLICATE 👍+1

这么多回答,也就俩人说到了事务

我发现使用了事务以后,速度反而慢下来了。
我用 100 个 goroute 一条一条更新,基本上可以实现 1 秒 1000 行。

使用了事务以后,还是 100 个 goroute,每个事物 1000 条,一个事务执行完成需要 1 分钟左右。

说到这里推荐大家一个帖子,几年前看到的,当时受益匪浅:
https://stackoverflow.com/questions/1711631/improve-insert-per-second-performance-of-sqlite

在Go语言中处理大规模数据更新(如200万条数据)到MySQL时,正确的姿势涉及多个方面,以确保性能和可靠性。以下是一些关键步骤和建议:

  1. 批量更新:直接一次性更新200万条数据可能会导致数据库锁定和性能问题。建议采用批量更新的方法,每次更新一定数量(如几千到几万条)的数据。

  2. 事务控制:如果更新逻辑需要保证数据一致性,可以使用事务。但请注意,长时间运行的事务可能占用大量资源,需根据具体情况权衡。

  3. 索引优化:确保更新涉及的字段上有适当的索引,以加速查询和更新操作。

  4. 避免高峰时段:在系统低峰时段执行大规模更新,以减少对正常业务的影响。

  5. 日志和监控:记录更新过程中的日志,并监控数据库的性能指标,以便及时发现问题并进行调整。

  6. 错误处理:在代码中添加完善的错误处理逻辑,确保在更新失败时能回滚或重试。

  7. 分片更新:如果数据量巨大,可以考虑将数据按某种逻辑分片,然后逐一更新每个分片。

  8. 使用工具:考虑使用专门的ETL(Extract, Transform, Load)工具或脚本语言(如Python结合pandas)来预处理数据,然后批量更新到MySQL。

总之,大规模数据更新需要综合考虑性能、可靠性和系统稳定性。根据具体情况选择合适的策略,并进行充分的测试和优化。

回到顶部