mysql数据快速导入doris

mysql数据快速导入doris

背景

前段时间业务需要将mysql数据导入到doris ,以便大数据平台使用

问题

本来想法很简单,doris 语法兼容mysql,将数据导出为insert 语句,直接插入就行。

想法不错,但是奈何数据量大(200多w),插入几个钟头还没完事。后来想想,试试批量insert语句。也挺慢。听说csv导入doris挺快的,奈何又遇到分隔符问题,简单说就是没有转义,导致列数不对。难道没有完美方法

解决

办法还是有的,可以将插入sql转换为csv,并转义(分界符),代码如下:

handlebars 复制代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 简化但可靠的SQL转CSV工具
 */
public class InsertSqlToCSV {

    public static void main(String[] args) throws IOException {
        if (args.length < 2) {
            System.out.println("使用方法: java InsertSqlToCSV <输入SQL文件> <输出CSV文件>");
            return;
        }

        convertSqlToCsv(args[0], args[1]);
    }

    public static void convertSqlToCsv(String sqlFile, String csvFile) throws IOException {
        List<String[]> allData = new ArrayList<>();
        List<String> headers = null;
        int totalRows = 0;
        int successRows = 0;
        int errorRows = 0;

        System.out.println("开始处理: " + sqlFile);

        try (BufferedReader br = new BufferedReader(new FileReader(sqlFile));
             FileWriter fw = new FileWriter(csvFile)) {

            String line;
            int lineNum = 0;

            while ((line = br.readLine()) != null) {
                lineNum++;
                line = line.trim();

                if (line.toUpperCase().startsWith("INSERT INTO")) {
                    totalRows++;

                    try {
                        // 提取字段名(只从第一行提取)
                        if (headers == null) {
                            headers = extractHeaders(line);
                            System.out.println("提取到 " + headers.size() + " 个字段");
                        }

                        // 提取VALUES部分
                        String valuesPart = extractValuesPart(line);

                        // 解析VALUES
                        List<String> values = parseSimple(valuesPart);

                        // 验证列数
                        if (values.size() != headers.size()) {
                            System.out.println("第 " + lineNum + " 行: 期望 " + headers.size() + " 列,实际 " + values.size() + " 列");
                            errorRows++;
                            continue;
                        }

                        allData.add(values.toArray(new String[0]));
                        successRows++;

                    } catch (Exception e) {
                        System.out.println("第 " + lineNum + " 行解析失败: " + e.getMessage());
                        errorRows++;
                    }
                }
            }

            System.out.println("\n处理完成:");
            System.out.println("总INSERT语句: " + totalRows);
            System.out.println("成功: " + successRows);
            System.out.println("失败: " + errorRows);

            if (successRows == 0) {
                System.out.println("没有成功解析的数据!");
                return;
            }

            // 写入CSV
            writeCsv(fw, headers, allData);
            System.out.println("CSV文件已生成: " + csvFile);

        } catch (Exception e) {
            System.err.println("处理失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 提取字段名
     */
    private static List<String> extractHeaders(String sql) {
        List<String> headers = new ArrayList<>();

        // 找到字段列表的开始
        int start = sql.indexOf('(');
        if (start == -1) return headers;

        // 找到字段列表的结束
        int end = sql.indexOf(')', start);
        if (end == -1) return headers;

        String headerPart = sql.substring(start + 1, end).trim();

        // 简单分割字段名
        String[] parts = headerPart.split(",");
        for (String part : parts) {
            String header = part.trim();
            // 移除引号
            if (header.startsWith("`") && header.endsWith("`")) {
                header = header.substring(1, header.length() - 1);
            } else if (header.startsWith("\"") && header.endsWith("\"")) {
                header = header.substring(1, header.length() - 1);
            } else if (header.startsWith("'") && header.endsWith("'")) {
                header = header.substring(1, header.length() - 1);
            } else if (header.startsWith("[") && header.endsWith("]")) {
                header = header.substring(1, header.length() - 1);
            }
            headers.add(header);
        }

        return headers;
    }

    /**
     * 提取VALUES部分
     */
    private static String extractValuesPart(String sql) {
        // 找到VALUES关键字
        int valuesIndex = sql.toUpperCase().indexOf("VALUES");
        if (valuesIndex == -1) {
            throw new IllegalArgumentException("未找到VALUES关键字");
        }

        String afterValues = sql.substring(valuesIndex + "VALUES".length()).trim();

        // 移除末尾分号
        if (afterValues.endsWith(";")) {
            afterValues = afterValues.substring(0, afterValues.length() - 1);
        }

        // 找到括号内容
        int openParen = afterValues.indexOf('(');
        int closeParen = afterValues.lastIndexOf(')');

        if (openParen == -1 || closeParen == -1 || closeParen <= openParen) {
            throw new IllegalArgumentException("未找到有效的VALUES括号");
        }

        return afterValues.substring(openParen + 1, closeParen).trim();
    }

    /**
     * 简单但可靠的解析器
     */
    private static List<String> parseSimple(String valuesStr) {
        List<String> values = new ArrayList<>();
        StringBuilder current = new StringBuilder();
        boolean inQuotes = false;
        char quoteChar = '\0';

        for (int i = 0; i < valuesStr.length(); i++) {
            char c = valuesStr.charAt(i);

            if (!inQuotes) {
                // 不在引号内
                if (c == '\'' || c == '"') {
                    inQuotes = true;
                    quoteChar = c;
                    current.append(c);
                } else if (c == ',') {
                    // 字段分隔符
                    String value = cleanValue(current.toString().trim());
                    values.add(value);
                    current.setLength(0);
                } else {
                    current.append(c);
                }
            } else {
                // 在引号内
                current.append(c);

                if (c == quoteChar) {
                    // 检查是否是转义引号
                    if (i + 1 < valuesStr.length() && valuesStr.charAt(i + 1) == quoteChar) {
                        // 是转义引号,跳过下一个
                        current.append(quoteChar);
                        i++;
                    } else {
                        // 是结束引号
                        inQuotes = false;
                    }
                }
            }
        }

        // 最后一个值
        if (current.length() > 0) {
            String value = cleanValue(current.toString().trim());
            values.add(value);
        }

        return values;
    }

    /**
     * 清理值
     */
    private static String cleanValue(String value) {
        value = value.trim();

        if (value.equalsIgnoreCase("NULL")) {
            return "";
        }

        // 移除外层引号
        if ((value.startsWith("'") && value.endsWith("'")) ||
                (value.startsWith("\"") && value.endsWith("\""))) {
            value = value.substring(1, value.length() - 1);
            // 处理转义引号
            value = value.replace("''", "'").replace("\"\"", "\"");
        }

        return value;
    }

    /**
     * 写入CSV
     */
    private static void writeCsv(FileWriter writer, List<String> headers, List<String[]> data)
            throws IOException {
        // 写入表头
        List<String> escapedHeaders = new ArrayList<>();
        for (String header : headers) {
            escapedHeaders.add(escapeCsv(header));
        }
        writer.write(String.join(",", escapedHeaders));
        writer.write("\n");

        // 写入数据
        for (String[] row : data) {
            List<String> escapedRow = new ArrayList<>();
            for (String cell : row) {
                escapedRow.add(escapeCsv(cell));
            }
            writer.write(String.join(",", escapedRow));
            writer.write("\n");
        }
    }

    /**
     * CSV转义
     */
    private static String escapeCsv(String value) {
        if (value == null) return "";

//        if (value.contains(",") || value.contains("\"") ||
//                value.contains("\n") || value.contains("\r")) {
//            value = value.replace("\"", "\"\"");
//            return "\"" + value + "\"";
//        }
        // 移除可能已经存在的外层单引号
        String processed = value.trim();
        boolean hadQuotes = false;

        if (processed.startsWith("'") && processed.endsWith("'") && processed.length() > 1) {
            processed = processed.substring(1, processed.length() - 1);
            hadQuotes = true;
        }

        // 转义处理
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < processed.length(); i++) {
            char c = processed.charAt(i);
            if (c == '\\') {
                // 反斜杠转义
                result.append("\\\\");
            } else if (c == '\'') {
                // 单引号转义
                result.append("\\'");
            } else {
                result.append(c);
            }
        }

        // 如果原始值有引号或者我们需要加引号,就加上
        if (hadQuotes || !processed.isEmpty()) {
            return "'" + result.toString() + "'";
        } else {
            return result.toString();
        }
    }

    
}

这样处理后,250万行的sql转csv,大概10分钟左右,然后csv导入doris ,几秒就行

最后

如果那位有更好办法,可以联系我:lita2lz

相关推荐
小湘西2 小时前
数仓分层架构详解2:ODS、DWD、DWS
大数据·数据库·数据仓库
十六年开源服务商2 小时前
WordPress网站模板设计完整指南
android
鹿角片ljp2 小时前
Java深入理解MySQL数据库操作
java·mysql·adb
YangYang9YangYan2 小时前
大数据专业就业指南
大数据
打破砂锅问到底0072 小时前
Claude--AI领域的安全优等生
大数据·人工智能·机器学习·ai
听风吹雨yu2 小时前
YoloV11的pt模型转rknn模型适用于RK3588等系列
linux·python·yolo·开源·rknn
A黑桃2 小时前
Paimon Action Jar 实现机制分析
大数据·后端
@我们的天空2 小时前
【FastAPI 完整版】路由与请求参数详解(query、path、params、body、form 完整梳理)- 基于 FastAPI 完整版
后端·python·pycharm·fastapi·后端开发·路由与请求