数据库同步数据工具

任务描述:
近期公司搞了一个Doris测试环境,用于项目上线前的测试。但是测试环境中没有任何数据,因此如果要项目测试需要将正式环境中的数据
同步到测试环境。

在开发此工具之前,同步数据是根据项目需求,将需要同步的表,在Doris正式环境中手动建表,再通过dass平台同步数据。

简单来说,同步的步骤是:

  1. 拿到前端、后端需要的表名
  2. 在Doris正式环境中获取表的DDL
  3. 在测试环境中创建表
  4. 在Dass平台同步数据

通过此工具可简化步骤123。可以某些表的步骤4(数据量小的表,10MB以下)。
接下来介绍核心代码和使用方法。

瓶颈、优化分析

  • 初步认为瓶颈在于网络IO,Dass平台的JVM也开到8G同步是很快的。由于同步程序跑在本地16G内存的笔记本上,JVM应该是可以开到8G的。
    那么瓶颈就在于程序的读写瓶颈。读写都是通过网络IO流,笔记本通过无线WIFI连接到的源服务器和目标服务器,受限于WIFI的带宽,
    读写速度都没有很快。

  • 使用有线网代替无线WIFI(待尝试)

  • 使用Java多线程同步(待尝试)

导出数据库所有表的DDL

import java.sql.*;

public class GetTablesDDLExampleMain {

    public static void main(String[] args) throws SQLException {
        // 要同步的数据库名
        String dataBaseName = "dwd";
        Connection connection = DriverManager.getConnection(Config.getSrcUrl(dataBaseName), Config.SRC_USER, Config.SRC_PASSWORD);
        TableUtils.getDataBaseTablesDDL(connection,dataBaseName);
    }
}

同步表数据使用样例

import java.sql.*;
import java.util.List;

public class DorisDataMigratorMain {

    public static void main(String[] args) {
        // 要同步的数据库名、表名
        String dataBaseName = Table2Sync.dataBaseName;
        List<String> tables = Table2Sync.TABLES_TO_SYNC;

        System.out.println("即将同步"+ dataBaseName + "以下表:" + tables);
        try {
            // 获取数据库连接
            Connection sourceConn = DriverManager.getConnection(Config.getSrcUrl(dataBaseName), Config.SRC_USER, Config.SRC_PASSWORD);
            Connection targetConn = DriverManager.getConnection(Config.getTargetUrl(dataBaseName), Config.TARGET_USER, Config.TARGET_PASSWORD);

            // 处理表
            for (String tableName : tables) {
                if (!TableUtils.tableExists(sourceConn, tableName)) {
                    System.err.println("源数据库中不存在表:" + tableName);
                    continue;
                }
                if (!TableUtils.tableExists(targetConn, tableName)) {
                    System.err.println("目标数据库中不存在表:" + tableName);
                    continue;
                }
                System.out.println("正在迁移表:" + tableName);

                // 清空目标表
                // truncateTable(targetConn, tableName);

                // 开始迁移数据
                TableUtils.migrateTableData(sourceConn, targetConn, tableName);
            }
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
    }


}

Table2Sync

用于配置需要同步的数据库和表

import java.util.Arrays;
import java.util.List;

public class Table2Sync {

    // 制定要同步的数据库
    public static final String dataBaseName = "databaseName";

    // 指定需要迁移的表名列表
    public static final List<String> TABLES_TO_SYNC = Arrays.asList(
            "table1",
            "table2",
            "table3",
            "table4",
            "table5
    );
}

Config类

主要用于配置数据源(ip,port,数据库用户名,密码)和目标数据源(ip,port,数据库用户名,密码)

public class Config {

    // 单次批量导入数据量
    public static final int BATCT_SIZE = 300;

    // 源数据库配置信息(Doris正式环境)
    public static final String SRC_USER = "src_username";
    public static final String SRC_PASSWORD = "src_passwd";
    public static String srcHost = "1.2.3.4";
    public static String srcPort = "1111"; // MySQL 协议端口


    // 目标数据库配置信息(Doris测试环境)
    public static final String TARGET_USER = "target_username";
    public static final String TARGET_PASSWORD = "target_passwd";
    public static final String targetHost = "5.6.7.8";
    public static final String targetPort = "2222";


    // 源库URL
    public static String getSrcUrl(String dataBaseName) {
        return "jdbc:mysql://" + srcHost + ":" + srcPort + "/" + dataBaseName + "?useSSL=false&characterEncoding=UTF-8";
    }

    // 目标库URL
    public static String getTargetUrl(String dataBaseName) {
        return "jdbc:mysql://" + targetHost + ":" + targetPort + "/" + dataBaseName + "?useSSL=false&characterEncoding=UTF-8";
    }
}

TableUtils类

核心方法

// 迁移表数据
public static void migrateTableData(Connection sourceConn, Connection targetConn, String tableName) throws SQLException
//  检查表是否存在
public static boolean tableExists(Connection conn, String tableName) throws SQLException
// 清空目标表数据
public static void truncateTable(Connection conn, String tableName) throws SQLException
// 导出数据库中所有表的建表语句(DDL)
public static void getDataBaseTablesDDL(Connection conn, String dataBaseName)
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.sql.*;

public class TableUtils {


    // 从源表读取数据,插入到目标表中
    public static void migrateTableData(Connection sourceConn, Connection targetConn, String tableName) throws SQLException {
        String selectSql = "SELECT * FROM " + tableName;

        try (
                Statement sourceStmt = sourceConn.createStatement();
                ResultSet rs = sourceStmt.executeQuery(selectSql)
        ) {
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();

            // 构造 INSERT SQL(显式列出字段更安全,这里用 ? 占位符)
            StringBuilder placeholders = new StringBuilder();
            for (int i = 1; i <= columnCount; i++) {
                placeholders.append("?");
                if (i < columnCount) placeholders.append(", ");
            }

            String insertSql = "INSERT INTO " + tableName + " VALUES (" + placeholders.toString() + ")";
            try (PreparedStatement ps = targetConn.prepareStatement(insertSql)) {

                int count = 0;
                while (rs.next()) {
                    for (int i = 1; i <= columnCount; i++) {
                        ps.setObject(i, rs.getObject(i)); // 👉 从源表拿数据
                    }
                    ps.addBatch(); // 添加到批量插入
                    if (++count % Config.BATCT_SIZE == 0) { // 每1000条提交一次(避免内存溢出)
                        ps.executeBatch();
                        System.out.println("已插入 " + count + " 条记录");
                    }
                }
                ps.executeBatch(); // 提交剩余的数据
                System.out.println("迁移完成,共插入 " + count + " 条记录");
            }
        }
    }

    // 检查表是否存在
    public static boolean tableExists(Connection conn, String tableName) throws SQLException {
        DatabaseMetaData metaData = conn.getMetaData();
        ResultSet rs = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
        boolean exists = rs.next();
        rs.close();
        return exists;
    }

    // 清空目标表
    public static void truncateTable(Connection conn, String tableName) throws SQLException {
        String sql = "TRUNCATE TABLE " + tableName;
        try (Statement stmt = conn.createStatement()) {
            stmt.executeUpdate(sql);
            System.out.println("已清空目标表:" + tableName);
        }
    }


    // 导出数据库中所有表的建表语句
    // 注意:导出的建表语句需要手动加上数据库名称
    public static void getDataBaseTablesDDL(Connection conn, String dataBaseName) {
        try{
            Statement stmt = conn.createStatement();
            ResultSet tables = stmt.executeQuery("SHOW TABLES");

            BufferedWriter writer = new BufferedWriter(new FileWriter("./SQL/" +dataBaseName + ".sql"));

            while (tables.next()) {
                String tableName = tables.getString(1);
                String showCreateSql = "SHOW CREATE TABLE " + dataBaseName + "." + tableName;

                Statement createStmt = conn.createStatement();
                ResultSet rs = createStmt.executeQuery(showCreateSql);

                if (rs.next()) {
                    String createTableSql = rs.getString(2);
                    System.out.println("-- 表: " + tableName);
                    System.out.println(createTableSql + ";\n");
                    writer.write("-- 表: " + tableName);
                    writer.newLine();
                    writer.write(createTableSql + ";");
                    writer.newLine();
                    writer.newLine();
                }
                rs.close();
                createStmt.close();
            }
            writer.close();
            System.out.println("建表语句已导出至" + dataBaseName + ".sql");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐