游客发表
一亿条数据往MySQL里灌,表插第一反应是入亿“这得搞到猴年马月”。其实真操作起来,条数瓶颈不在插入速度,据操而在你用什么姿势插。表插
最蠢的入亿办法是一条条insert,那得插到天荒地老。条数我见过有哥们儿写了个循环,据操跑了三天三夜还没完,表插最后锁表把线上业务全堵死了。入亿正确姿势是条数批量插入,一次塞个几千条,据操commit一次。表插别小看这个动作,入亿同样是条数insert,批量能比单条快上几十倍。原理很简单,每条insert都要走一遍解析、权限检查、事务日志,这些开销摊到一万条上就几乎可以忽略不计了。
但光有批量还不够。你那个表要是带了一堆索引,插一条数据就得维护一次B+树,插一亿条就是维护一亿次。这感觉就像你一边盖楼一边往墙里塞钢筋,每塞一根都得重新量一遍尺寸。所以生产环境里最狠的做法是:先drop掉所有非主键索引,插完数据再重建。别心疼那点重建时间,比你带着索引硬插快太多了。
还有个坑是主键。如果你用自增id,那没问题,MySQL天生就喜欢顺序插入。但如果你主键是UUID那种随机字符串,那完蛋了——每次插入都可能触发页分裂,磁盘随机写,性能直接腰斩再腰斩。我吃过这个亏,当时插两千万数据用了四十多分钟,后来改成有序id,五分钟就完事了。所以能换自增id就换,换不了就考虑用雪花算法生成趋势递增的id。
再说说事务。别傻乎乎地开一个大事务,插一亿条不commit,那你的undo log会把磁盘撑爆,而且一旦回滚,数据库得哭给你看。正确做法是每插个几万条就commit一次,这样就算中途挂了,损失也就那几万条,重跑一遍就行。这里有个细节,commit太频繁也不行,比如每插一百条就commit,那fsync的次数会拖慢整体速度,我自己试下来,五万条一个事务比较合适,具体得看你的redo log大小和磁盘性能。
还有个偏门技巧,如果你那台机器内存够大,可以先关掉binlog。一亿条insert的binlog得占好几十G空间,写binlog本身也是IO开销。当然,这仅限于你确定不需要数据恢复的场景。我干过一次,关掉binlog后插入速度直接翻倍,插完再打开binlog,做个全量备份,效果一样。
最后说个最容易被忽略的——你的表引擎。MyISAM插入确实比InnoDB快,因为它没有事务、没有行级锁。但你要敢在生产环境用MyISAM,那等于给自己埋雷,表锁能把并发读写全部卡死。所以老老实实用InnoDB,但可以把参数调一调:innodb_flush_log_at_trx_commit设为2,能减少磁盘刷盘次数;innodb_buffer_pool_size调到物理内存的70%左右,让数据尽量在内存里缓冲。
真到了极端情况,比如一亿条数据必须在半小时内灌完,那就别死磕单机了。用LOAD DATA INFILE,这个命令是MySQL的批量导入神器,比insert快一个数量级。前提是你的数据是结构化文件,比如CSV。我拿一个两千万行的CSV试过,LOAD DATA用了不到四十秒,而同数据用批量insert得跑十分钟。要是连LOAD DATA都嫌慢,那只能上并行导入,把文件切成十份,开十个线程同时导,最后几分搞定。
实际操作起来,你会遇到各种意想不到的幺蛾子,比如磁盘满了、内存溢出、超时断开。我的建议是写个带断点续传的脚本,每插完一批记录一下位置,失败了能从断点接着跑。别指望一次成功,能断点续传的导入脚本才是好脚本。
说到底,插一亿条数据不是技术难题,是工程问题。拆解成小批次、减少索引维护、调整MySQL参数、选对导入方式,这四件事做对了,剩下的就是时间问题。
友情链接