8月22号写了一篇txt到mysql的程序,有个小老弟私信我说太慢了,今天对这段程序做了一个优化在处理大规模数据导入时,性能优化是一个至关重要的方面。本文将分享如何通过简单而有效的方式,将一个原本需要337秒导入1万条数据的Java程序优化到仅需2秒的经验
首先来看一下老程序
package practise.utils;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Duration;
import java.time.LocalDateTime;
public class TxtToDatabase {
/**
* 将TXT文件导入到MySQL数据库中的通用方法
*
* @param jdbcURL MySQL连接URL
* @param dbUser 数据库用户名
* @param dbPassword 数据库密码
* @param tableName 表名
* @param columns 列名(以逗号分隔的字符串)
* @param filePath TXT文件路径
* @param delimiter TXT文件中的分隔符
*/
public static void importTxtToDatabase(String jdbcURL, String dbUser, String dbPassword,
String tableName, String columns, String filePath, String delimiter) {
// 构造SQL插入语句
String sql = generateInsertSQL(tableName, columns);
try (Connection connection = DriverManager.getConnection(jdbcURL, dbUser, dbPassword);
BufferedReader reader = new BufferedReader(new FileReader(filePath));
PreparedStatement statement = connection.prepareStatement(sql)) {
String line;
while ((line = reader.readLine()) != null) {
String[] data = line.split(delimiter);
// 为PreparedStatement设置参数
for (int i = 0; i < data.length; i++) {
statement.setString(i + 1, data[i]);
}
// 执行SQL插入命令
statement.executeUpdate();
}
System.out.println("Data has been inserted successfully into table " + tableName);
} catch (SQLException | IOException e) {
e.printStackTrace();
}
}
/**
* 生成INSERT SQL语句
*
* @param tableName 表名
* @param columns 列名
* @return 生成的SQL语句
*/
private static String generateInsertSQL(String tableName, String columns) {
String[] columnArray = columns.split(",");
StringBuilder placeholders = new StringBuilder();
for (int i = 0; i < columnArray.length; i++) {
placeholders.append("?");
if (i < columnArray.length - 1) {
placeholders.append(",");
}
}
return "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + placeholders.toString() + ")";
}
public static void main(String[] args) {
// 示例参数
String jdbcURL = "jdbc:mysql://localhost:3306/zhenbutong";
String dbUser = "root";
String dbPassword = "root";
String tableName = "user";
String columns = "id,username,password";
String filePath = "D:\\tmp\\user.txt";
String delimiter = "\\|";
LocalDateTime startTime = LocalDateTime.now();
System.out.println("开始执行:" + startTime);
// 调用通用方法导入数据
importTxtToDatabase(jdbcURL, dbUser, dbPassword, tableName, columns, filePath, delimiter);
LocalDateTime endTime = LocalDateTime.now();
System.out.println("执行结束:" + endTime);
// 计算执行时长(以秒为单位)
Duration duration = Duration.between(startTime, endTime);
long seconds = duration.getSeconds();
System.out.println("总共执行了:" + seconds + " 秒");
}
}
这段程序执行的结果是337秒
初始方案与问题分析
原始代码逐条将TXT文件中的数据插入到MySQL数据库。这种方法简单直接,但当数据量大时,性能问题凸显。逐条插入不仅增加了数据库的网络开销,还导致事务处理效率低下。
以下是原始代码的核心逻辑:
while ((line = reader.readLine()) != null) {
String[] data = line.split(delimiter);
for (int i = 0; i < data.length; i++) {
statement.setString(i + 1, data[i]);
}
statement.executeUpdate();
}
问题:每插入一行数据,程序都会向数据库发送一次请求并执行一次事务提交,这样的频繁交互导致了极大的性能损耗。
优化策略
要解决这个问题,我们采用了以下策略:
-
批量插入(Batch Insert):将多条SQL语句放入批处理中,然后一次性执行。这减少了数据库交互的次数,大幅提高了插入效率。
-
关闭自动提交:将数据库连接的自动提交功能关闭,手动管理事务提交。这样做可以减少数据库在处理每条记录时的事务开销。
-
调整批处理大小:在我们的示例中,选择了每500条记录执行一次批处理,这个数字根据实际环境可以调整。
优化后的代码实现
以下是优化后的代码实现:
package practise.utils;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Duration;
import java.time.LocalDateTime;
public class TxtToDatabaseBatch {
/**
* 将TXT文件导入到MySQL数据库中的通用方法
*
* @param jdbcURL MySQL连接URL
* @param dbUser 数据库用户名
* @param dbPassword 数据库密码
* @param tableName 表名
* @param columns 列名(以逗号分隔的字符串)
* @param filePath TXT文件路径
* @param delimiter TXT文件中的分隔符
*/
public static void importTxtToDatabase(String jdbcURL, String dbUser, String dbPassword,
String tableName, String columns, String filePath, String delimiter) {
// 构造SQL插入语句
String sql = generateInsertSQL(tableName, columns);
try (Connection connection = DriverManager.getConnection(jdbcURL, dbUser, dbPassword);
BufferedReader reader = new BufferedReader(new FileReader(filePath));
PreparedStatement statement = connection.prepareStatement(sql)) {
connection.setAutoCommit(false); // 关闭自动提交,手动管理事务
String line;
int batchSize = 500; // 批处理大小
int count = 0;
while ((line = reader.readLine()) != null) {
String[] data = line.split(delimiter);
// 为PreparedStatement设置参数
for (int i = 0; i < data.length; i++) {
statement.setString(i + 1, data[i]);
}
statement.addBatch(); // 添加到批处理中
count++;
if (count % batchSize == 0) {
statement.executeBatch(); // 批量执行
connection.commit(); // 提交事务
}
}
// 处理剩余的数据
statement.executeBatch();
connection.commit();
System.out.println("Data has been inserted successfully into table " + tableName);
} catch (SQLException | IOException e) {
e.printStackTrace();
}
}
/**
* 生成INSERT SQL语句
*
* @param tableName 表名
* @param columns 列名
* @return 生成的SQL语句
*/
private static String generateInsertSQL(String tableName, String columns) {
String[] columnArray = columns.split(",");
StringBuilder placeholders = new StringBuilder();
for (int i = 0; i < columnArray.length; i++) {
placeholders.append("?");
if (i < columnArray.length - 1) {
placeholders.append(",");
}
}
return "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + placeholders.toString() + ")";
}
public static void main(String[] args) {
// 示例参数
String jdbcURL = "jdbc:mysql://localhost:3306/zhenbutong";
String dbUser = "root";
String dbPassword = "root";
String tableName = "user";
String columns = "id,username,password";
String filePath = "D:\\tmp\\user1.txt";
String delimiter = "\\|";
LocalDateTime startTime = LocalDateTime.now();
System.out.println("开始执行:" + startTime);
// 调用通用方法导入数据
importTxtToDatabase(jdbcURL, dbUser, dbPassword, tableName, columns, filePath, delimiter);
LocalDateTime endTime = LocalDateTime.now();
System.out.println("执行结束:" + endTime);
// 计算执行时长(以秒为单位)
Duration duration = Duration.between(startTime, endTime);
long seconds = duration.getSeconds();
System.out.println("总共执行了:" + seconds + " 秒");
}
}
优化后的执行结果
优化效果
在应用以上优化后,程序的执行时间从337秒锐减至2秒,性能提升近170倍。这一提升不仅显著减少了处理时间,还提高了程序的可扩展性,使其能够更高效地处理更大规模的数据集。
性能优化的思考
-
批量处理与批次大小:批量处理的核心在于减少数据库的交互次数。根据系统资源和数据库性能,合理设置批处理大小能够避免内存溢出,并最大化性能。
-
事务管理:合理的事务管理可以减少数据库在处理每次插入时的开销。在大规模插入操作中,批量提交事务比每条数据都提交要高效得多。
-
数据库配置与硬件资源:在实际应用中,还可以通过调整数据库的配置(如
innodb_buffer_pool_size
、innodb_log_file_size
等)和优化硬件资源(如内存、磁盘I/O等),进一步提升性能。
总结
通过批量插入和事务管理,本文展示了如何有效提升Java程序在大规模数据导入时的性能。虽然本文案例以MySQL为例,但这些优化策略同样适用于其他关系型数据库。在日常开发中,我们应始终关注性能瓶颈,并通过合理的优化手段来提升程序效率。希望这些经验能为您在项目中的性能优化带来启发。