我跟你说啊,这事儿其实就是上周在工位那会儿聊起来的,我们组那个老朱,搞个ETL导入任务,硬是跑了一晚上,插了不到一个亿的数据,还美滋滋地说“还行”。我听了直接脑瓜子嗡嗡的,你说你搞MySQL,插入慢成那样,还不赶紧看看方案?
不要天真地一条条插,批量才是王道
你别跟我说什么for循环一条条insert,那是给小作坊写脚本的。我看了老朱的代码,典型的:
for (int i = 0; i < data.size(); i++) {
String sql = "INSERT INTO big_table (col1, col2) VALUES (?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, data.get(i).getCol1());
stmt.setString(2, data.get(i).getCol2());
stmt.executeUpdate();
}
我当场就说:“哥你这样能快我直播吃键盘”,不是说你写得不对啊,是你这思路就有问题。
JDBC批处理,最低配也得整这个
我们当时就现场改,用addBatch()配合executeBatch(),这玩意跟吃药似的,整瓶吃才有效。
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
PreparedStatement stmt = conn.prepareStatement("INSERT INTO big_table (col1, col2) VALUES (?, ?)");
for (int i = 0; i < data.size(); i++) {
stmt.setString(1, data.get(i).getCol1());
stmt.setString(2, data.get(i).getCol2());
stmt.addBatch();
if (i % 10000 == 0) {
stmt.executeBatch();
conn.commit(); // 不commit等着吃内存炸弹吧
stmt.clearBatch();
}
}
stmt.executeBatch();
conn.commit();
stmt.close();
conn.close();
我们改完那套代码之后,一晚上能插4亿多,那速度,老朱第二天都开始考虑换工作了。
更极限一点?直接写CSV文件导入
这个是真的骚操作。有时候你要插的是真·亿级数据,这种JDBC插法哪怕批处理都嫌慢,那就别装了,乖乖走LOAD DATA INFILE:
LOAD DATA INFILE '/path/to/data.csv'
INTO TABLE big_table
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
(col1, col2);
你Java这边就输出CSV,随便用个BufferedWriter拼:
BufferedWriter writer = new BufferedWriter(new FileWriter("data.csv"));
for (MyData item : data) {
writer.write(item.getCol1() + "," + item.getCol2());
writer.newLine();
}
writer.close();
这个方式我们测过,一小时导10亿不是梦,MySQL自己把CSV吞了,那才是数据库真正擅长的事情。
不要忘了关掉你不该开的东西
对,还有个坑,很多人忘了插数据前关日志、关索引。你一边建索引一边插,那就不是插数据,是在杀服务器。
ALTER TABLE big_table DISABLE KEYS;
-- 然后导入
ALTER TABLE big_table ENABLE KEYS;
还有binlog、foreign key、autocommit啥的,能关的先关,记得导完再开,不然插一行卡半秒。
最后一句:MySQL不是万能的
我说真的,如果你每天都得干这种10亿数据的活儿,你是不是该考虑用ClickHouse或者专门搞个Kafka + Flink + Hive那套玩意了?老朱最后都去问大数据组借机器了,人家直接说一句“为啥不用Hudi?”他当场脸绿。
行了,先聊到这,我这会儿去泡杯咖啡,一会儿还有个工单要看,你们要是实在插不进去,回头来我桌上,咱们抠SQL调内核一起看。