现状分析
之前在mysql业务库,导出报表会出现各种表相互关联,导致夯死的情况
改进使用ClickHouse做宽表
后面使用binlog监听,洗数据洗成一张宽表,存放在ck中,但是发现超过一定数量级100w也会很卡慢,但是比mysql强的是可以出来。
如何导出300w 、500w 1000w数据量级
查看了CK有个特性,执行流式数据处理,可以使用这个特性来处理
方案 1:分批查询
如果你当前的做法是 一次性查询 100W 条数据,那就容易导致 CK 查询压力大、内存占用高。可以 分批查询,用 流式处理(如 LIMIT … OFFSET 或游标)优化导出。
SELECT * FROM my_table WHERE 条件 ORDER BY id LIMIT 10000 OFFSET 0;
SELECT * FROM my_table WHERE 条件 ORDER BY id LIMIT 10000 OFFSET 10000;
...
# 使用 游标方式 进行分页,例如:
SELECT * FROM my_table WHERE 条件 AND id > last_id ORDER BY id LIMIT 10000;
这种方案比较繁琐,效果也不好
方案 2:ClickHouse 外部表流式查询
ClickHouse 提供 流式查询 + CSV 导出,这样数据 不会全部加载到内存,而是边查边写,提高效率。
clickhouse-client --user username --password password --query="SELECT page_name,page_path FROM table WHERE type='visit'" --format CSV > export11.csv
📌 优势
不会占用太多内存(不像 SELECT * 一次性取 100W 数据)。
查询结果可以直接写入文件,减少网络传输压力。
测试结果 百万级数据秒级生成
方案 3:异步任务 + 预生成文件
(适合超大量数据)
如果查询仍然 导致性能下降,可以考虑 后台异步处理,生成 CSV 文件后再让用户下载:
思路
用户提交导出请求后,后端 异步任务 开始查询并生成文件(存储到 OSS 或本地)。
任务完成后,给用户 返回下载链接,前端再下载文件。
实现
📌 Step 1: 后端异步查询并存 CSV
clickhouse-client --user username --password password --query="SELECT * FROM my_table WHERE 条件" --format CSV > /tmp/export_$(date +%s).csv
📌 Step 2: 文件存储到对象存储(如 MinIO / OSS)
aws s3 cp /tmp/export.csv s3://my-bucket/
📌 Step 3: 前端定期轮询下载链接 后端提供一个 API,比如:
{"status": "done","download_url": "https://oss.example.com/export_123.csv"
}
前端轮询这个 API,一旦任务完成,就给用户提供下载按钮。
CK原生命令集成到Java
和ck部署在同一个服务器上(不推荐)
import org.springframework.web.bind.annotation.*;
import java.io.*;@RestController
@RequestMapping("/clickhouse")
public class ClickHouseController {@GetMapping("/export")public String exportData() {String query = "SELECT * FROM my_table WHERE 条件";String exportFile = "/tmp/export.csv"; // 保存到临时目录String command = String.format("clickhouse-client --query=\"%s\" --format CSV > %s", query, exportFile);ProcessBuilder processBuilder = new ProcessBuilder("/bin/bash", "-c", command);try {Process process = processBuilder.start();int exitCode = process.waitFor();if (exitCode == 0) {return "✅ ClickHouse 数据导出成功:" + exportFile;} else {return "❌ ClickHouse 导出失败";}} catch (IOException | InterruptedException e) {return "⚠️ 发生异常:" + e.getMessage();}}
}
远程 SSH 连接到 ClickHouse 服务器
import com.jcraft.jsch.*;import java.io.*;public class RemoteClickHouseExecutor {private static final String SSH_HOST = "clickhouse_server_ip"; // 远程 ClickHouse 服务器 IPprivate static final String SSH_USER = "root"; // SSH 用户private static final String SSH_PASSWORD = "your_password"; // SSH 密码private static final String CLICKHOUSE_QUERY = "SELECT * FROM my_table WHERE 条件";private static final String EXPORT_FILE = "/tmp/export.csv"; // 远程服务器上的文件路径public static void main(String[] args) {try {JSch jsch = new JSch();Session session = jsch.getSession(SSH_USER, SSH_HOST, 22);session.setPassword(SSH_PASSWORD);session.setConfig("StrictHostKeyChecking", "no");session.connect();// 远程执行 ClickHouse 查询String command = "clickhouse-client --query=\"" + CLICKHOUSE_QUERY + "\" --format CSV > " + EXPORT_FILE;ChannelExec channel = (ChannelExec) session.openChannel("exec");channel.setCommand(command);channel.setInputStream(null);channel.setErrStream(System.err);InputStream inputStream = channel.getInputStream();channel.connect();// 读取命令执行结果BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));String line;while ((line = reader.readLine()) != null) {System.out.println(line);}channel.disconnect();session.disconnect();System.out.println("✅ ClickHouse 远程数据导出完成:" + EXPORT_FILE);} catch (Exception e) {e.printStackTrace();}}
}
ClickHouse JDBC 连接远程 ClickHouse
添加 ClickHouse JDBC 依赖
<dependency><groupId>ru.yandex.clickhouse</groupId><artifactId>clickhouse-jdbc</artifactId><version>0.3.2</version>
</dependency>
JDBC 查询 ClickHouse 并导出 CSV
import ru.yandex.clickhouse.ClickHouseDataSource;import java.io.*;
import java.sql.*;public class ClickHouseJdbcExport {private static final String CLICKHOUSE_URL = "jdbc:clickhouse://clickhouse_server_ip:8123/default";private static final String CLICKHOUSE_USER = "default";private static final String CLICKHOUSE_PASSWORD = "";public static void main(String[] args) {String query = "SELECT * FROM my_table WHERE 条件";String exportFile = "export.csv";try (Connection connection = new ClickHouseDataSource(CLICKHOUSE_URL).getConnection(CLICKHOUSE_USER, CLICKHOUSE_PASSWORD);Statement stmt = connection.createStatement();ResultSet rs = stmt.executeQuery(query);BufferedWriter writer = new BufferedWriter(new FileWriter(exportFile))) {ResultSetMetaData metaData = rs.getMetaData();int columnCount = metaData.getColumnCount();// 写入 CSV 头部for (int i = 1; i <= columnCount; i++) {writer.write(metaData.getColumnName(i));if (i < columnCount) writer.write(",");}writer.newLine();// 写入数据while (rs.next()) {for (int i = 1; i <= columnCount; i++) {writer.write(rs.getString(i));if (i < columnCount) writer.write(",");}writer.newLine();}System.out.println("✅ ClickHouse 数据已导出:" + exportFile);} catch (Exception e) {e.printStackTrace();}}
}