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