Java程序导入1万条数据到MySQL的性能提升170倍!

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();
}

问题:每插入一行数据,程序都会向数据库发送一次请求并执行一次事务提交,这样的频繁交互导致了极大的性能损耗。

优化策略

要解决这个问题,我们采用了以下策略:

  1. 批量插入(Batch Insert):将多条SQL语句放入批处理中,然后一次性执行。这减少了数据库交互的次数,大幅提高了插入效率。

  2. 关闭自动提交:将数据库连接的自动提交功能关闭,手动管理事务提交。这样做可以减少数据库在处理每条记录时的事务开销。

  3. 调整批处理大小:在我们的示例中,选择了每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倍。这一提升不仅显著减少了处理时间,还提高了程序的可扩展性,使其能够更高效地处理更大规模的数据集。

性能优化的思考
  1. 批量处理与批次大小:批量处理的核心在于减少数据库的交互次数。根据系统资源和数据库性能,合理设置批处理大小能够避免内存溢出,并最大化性能。

  2. 事务管理:合理的事务管理可以减少数据库在处理每次插入时的开销。在大规模插入操作中,批量提交事务比每条数据都提交要高效得多。

  3. 数据库配置与硬件资源:在实际应用中,还可以通过调整数据库的配置(如innodb_buffer_pool_sizeinnodb_log_file_size等)和优化硬件资源(如内存、磁盘I/O等),进一步提升性能。

总结

通过批量插入和事务管理,本文展示了如何有效提升Java程序在大规模数据导入时的性能。虽然本文案例以MySQL为例,但这些优化策略同样适用于其他关系型数据库。在日常开发中,我们应始终关注性能瓶颈,并通过合理的优化手段来提升程序效率。希望这些经验能为您在项目中的性能优化带来启发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值