SpringBoot应用中如何向MySQL导入大体积TSV数据?
解决大体积TSV数据加载时的OOM问题(SpringBoot + Liquibase)
针对19GB超大TSV文件加载时的堆内存溢出问题,可采用以下几种方案在应用启动时同步完成数据上传:
1. 优化Liquibase原生loadUpdateData配置
Liquibase的loadUpdateData支持分批处理参数,避免一次性加载全量数据到内存:
- 添加
batchSize参数控制单次批量操作的行数(例如batchSize="1000"),配合commitCount设置每N条数据提交一次事务 - 开启预编译语句
usePreparedStatements="true",提升批量操作效率同时降低内存占用
修改后的脚本示例:
<changeSet id="load-some-data" author="author"> <loadUpdateData encoding="UTF-8" file="data-file.tsv" primaryKey="q_name" separator=" " tableName="homology_data" batchSize="1000" commitCount="5000" usePreparedStatements="true"> <!-- 列定义保持不变 --> </loadUpdateData> </changeSet>
2. 使用MySQL原生LOAD DATA INFILE命令
利用数据库级别的批量导入能力,完全绕开Java堆内存限制,速度远高于Liquibase的Java端处理:
- 在Liquibase脚本中用
<sql>标签执行原生命令 - 需确保MySQL开启
local_infile参数,且JDBC连接URL添加allowLoadLocalInfile=true配置
脚本示例:
<changeSet id="load-large-data" author="author"> <sql> LOAD DATA LOCAL INFILE 'data-file.tsv' INTO TABLE homology_data CHARACTER SET UTF8 FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' (q_name, chr, start, end, ref_base, hom_region, hom_region_var_location, base_in_hom_region, pident, strand, gapped_alignment, created_at, updated_at); </sql> </changeSet>
3. 自定义SpringBoot启动加载逻辑
通过CommandLineRunner或ApplicationListener实现逐行读取+批量插入,完全控制内存占用:
- 使用
BufferedReader逐行读取TSV文件,每积累指定行数(如1000条)执行一次批量插入/更新 - 利用
JdbcTemplate的batchUpdate方法实现高效批量操作,配合ON DUPLICATE KEY UPDATE实现类似loadUpdateData的更新逻辑
示例代码:
@Component public class LargeDataLoader implements CommandLineRunner { @Autowired private JdbcTemplate jdbcTemplate; @Override public void run(String... args) throws Exception { String filePath = "classpath:data-file.tsv"; int batchSize = 1000; List<Object[]> batch = new ArrayList<>(batchSize); try (BufferedReader reader = new BufferedReader(new InputStreamReader( this.getClass().getResourceAsStream(filePath), StandardCharsets.UTF_8))) { String line; reader.readLine(); // 跳过表头(按需调整) while ((line = reader.readLine()) != null) { String[] fields = line.split("\t"); Object[] row = new Object[]{ fields[0], fields[1], Long.parseLong(fields[2]), Long.parseLong(fields[3]), fields[4], fields[5], fields[6], fields[7], Double.parseDouble(fields[8]), fields[9], fields[10], LocalDate.parse(fields[11]), LocalDate.parse(fields[12]) }; batch.add(row); if (batch.size() >= batchSize) { executeBatch(batch); batch.clear(); } } // 处理剩余数据 if (!batch.isEmpty()) { executeBatch(batch); } } } private void executeBatch(List<Object[]> batch) { jdbcTemplate.batchUpdate( "INSERT INTO homology_data (q_name, chr, start, end, ref_base, hom_region, hom_region_var_location, base_in_hom_region, pident, strand, gapped_alignment, created_at, updated_at) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE chr=VALUES(chr), start=VALUES(start), end=VALUES(end), ref_base=VALUES(ref_base)", batch ); } }
4. 拆分大文件
将19GB的TSV拆分为多个小文件(如每个1GB),通过Liquibase多changeSet或自定义逻辑逐个加载,降低单次加载的内存压力:
- Linux可使用
split命令:split -l 1000000 data-file.tsv data-part- - Windows可通过PowerShell脚本或第三方工具拆分
5. 临时调整JVM堆内存(治标方案)
若以上方案暂无法实施,可临时增大SpringBoot启动的堆内存参数,例如:
java -Xmx16g -jar your-app.jar
此方案仅适合临时应急,无法从根本解决大文件加载的内存效率问题。
内容的提问来源于stack exchange,提问作者Sayantan Ghosh
相关产品推荐
相关产品推荐

