
叙事主角:老李,AI 时代的互联网架构师,见过太多数据团队在 ClickHouse 的 JOIN 禁区里跌倒,在 StarRocks 商业版授权前止步,终于在 Doris 3.0 的存算分离里找到了回家的路。

千里之行,始于足下。数据之路,始于乱局。
老李接手过一个电商平台的数据架构:报表用 MySQL 分库分表跑,日志扔 Elasticsearch,用户画像放 HBase,偶尔还要跨库 JOIN。每次产品说"我要一个实时 GMV 看板",老李的心就凉半截——光数据同步脚本就要维护十几个,更别提凌晨三点报警了。
这是中国互联网数据架构的经典困局:数据孤岛多、实时性差、运维成本高。
直到老李把 Apache Doris 3.0 引进来,统一了 OLAP 分析、湖仓查询、日志检索三条链路。老板问"为什么报表快了",老李说:"因为我们不再用 Python 跑数了。"
工欲善其事,必先利其器。
Apache Doris 前身是百度 Palo,2018 年捐赠给 Apache 基金会。最新稳定版 3.0.6(2025年6月发布),基于 MPP 架构,提供亚秒级分析查询,支持高并发点查与复杂聚合,兼容 MySQL 协议,是 Unified Analytical Database 理念的代表产品。
功能模块 | 特性描述 | 使用场景 |
|---|---|---|
实时导入 | Stream Load / Routine Load / Flink Doris Connector | Kafka 实时写入、CDC 同步 |
物化视图 | 同步/异步物化视图,多表改写 | 报表加速、数据预聚合 |
湖仓一体 | 支持 Hive/Iceberg/Hudi/Paimon 外表查询 | 不搬数据直接分析 |
倒排索引 | 全文检索 + BKD Tree 数值索引 | 日志检索、用户搜索 |
主键模型 | UNIQUE KEY 支持 UPSERT,Merge On Write | 实时更新业务流水 |
存算分离 | 3.0 全面支持,计算节点无状态 | 云原生弹性扩缩容 |
半结构化 | VARIANT 类型 Schema Free 分析 | JSON/埋点日志 |
向量检索 | 2025 Roadmap 中 AI 向量索引支持 | RAG 检索增强生成 |
多租户 | Workload Group 资源隔离 | 多部门共享集群 |
跨源联邦 | External Catalog 统一元数据管理 | 多数据源无缝查询 |

名不正则言不顺,授权不明则风险不断。
Apache Doris 遵循 Apache License 2.0,这是最宽松的开源协议之一:
权限 | 说明 |
|---|---|
个人使用 | 完全免费,无任何限制 |
商业使用 | ✅ 允许,不需要开放源代码 |
修改分发 | ✅ 允许修改后分发,需保留原始许可证声明 |
专利授权 | ✅ 贡献者自动授予用户专利使用权 |
品牌限制 | ❌ 不得使用 Apache 商标做背书,不得暗示官方认可 |
责任免除 | 作者不承担任何损失责任 |
企业特别注意:SelectDB(Doris 商业化公司)提供企业版,含额外功能和 SLA。Apache 社区版 vs SelectDB 企业版的区别主要在运维工具、高级特性和技术支持层面。选型时务必区分"用 Apache 版"还是"用 SelectDB 版"。

不积跬步,无以至千里。先跑起来,再谈优化。
# docker-compose.yml
# Apache Doris 3.0 单机开发环境 + 监控套件
# 说明:生产环境请使用 3FE+3BE 高可用部署
version: '3.8'
networks:
doris-net:
driver: bridge
services:
# ============ FE 前端节点 ============
doris-fe:
image: apache/doris:3.0.6-fe-x86_64
container_name: doris-fe
hostname: doris-fe
environment:
- FE_SERVERS=fe1:doris-fe:9010
- FE_ID=1
ports:
- "8030:8030" # HTTP API / Web UI
- "9030:9030" # MySQL 协议端口
- "9010:9010" # FE 内部通信
volumes:
- doris-fe-data:/opt/apache-doris/fe/doris-meta
- doris-fe-log:/opt/apache-doris/fe/log
networks:
- doris-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8030/api/bootstrap"]
interval: 30s
timeout: 10s
retries: 5
restart: unless-stopped
# ============ BE 后端节点 ============
doris-be:
image: apache/doris:3.0.6-be-x86_64
container_name: doris-be
hostname: doris-be
environment:
- FE_SERVERS=fe1:doris-fe:9010
- BE_ADDR=doris-be:9050
ports:
- "8040:8040" # BE HTTP 端口(Tablet 管理)
- "9050:9050" # BE 心跳端口
volumes:
- doris-be-data:/opt/apache-doris/be/storage
- doris-be-log:/opt/apache-doris/be/log
networks:
- doris-net
depends_on:
doris-fe:
condition: service_healthy
restart: unless-stopped
# ============ Prometheus 监控 ============
prometheus:
image: prom/prometheus:v2.51.0
container_name: doris-prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
networks:
- doris-net
restart: unless-stopped
# ============ Grafana 可视化 ============
grafana:
image: grafana/grafana:10.4.0
container_name: doris-grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=doris123
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/dashboards:/var/lib/grafana/dashboards
- ./grafana/provisioning:/etc/grafana/provisioning
networks:
- doris-net
depends_on:
- prometheus
restart: unless-stopped
volumes:
doris-fe-data:
doris-fe-log:
doris-be-data:
doris-be-log:
prometheus-data:
grafana-data:# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'doris_fe'
static_configs:
- targets: ['doris-fe:8030']
metrics_path: '/metrics'
- job_name: 'doris_be'
static_configs:
- targets: ['doris-be:8040']
metrics_path: '/metrics'# 创建目录结构
mkdir -p doris-demo/{grafana/dashboards,grafana/provisioning/datasources}
cd doris-demo
# 复制上面两个配置文件后启动
docker compose up -d
# 等待约 60 秒,检查状态
docker compose ps
# 验证 FE 存活
mysql -uroot -P9030 -h127.0.0.1 \
-e "SELECT host, join, alive FROM frontends()"
# 验证 BE 存活
mysql -uroot -P9030 -h127.0.0.1 \
-e "SELECT host, alive FROM backends()"服务 | 地址 | 账号/密码 |
|---|---|---|
Doris FE Web UI | http://localhost:8030 | root / (空) |
Prometheus | http://localhost:9090 | - |
Grafana | http://localhost:3000 | admin / doris123 |
截图说明:Doris FE Web UI:
System Overview页面可查看 FE/BE 节点状态、Query Profile Grafana:导入 Doris 官方 Dashboard ID20196即可看到 QPS、延迟、内存等指标 Prometheus:在/graph页面输入doris_fe_query_total可查看查询统计
纸上得来终觉浅,绝知此事要躬行。
本节覆盖 Doris 五大核心功能的完整 Spring Boot 集成示例,每个功能配对应场景和简单页面。
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA / JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL Connector(Doris 兼容 MySQL 协议)-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Thymeleaf 模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- OkHttp(Stream Load 用)-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
</dependencies>spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:9030/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password:
thymeleaf:
cache: false
doris:
fe-host: http://127.0.0.1:8030
stream-load-url: http://127.0.0.1:8030/api/demo/orders/_stream_load-- 连接方式:mysql -uroot -P9030 -h127.0.0.1
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
-- 功能1:订单明细表(主键模型,支持 UPSERT)
CREATE TABLE IF NOT EXISTS orders (
order_id BIGINT NOT NULL COMMENT '订单ID',
user_id INT NOT NULL COMMENT '用户ID',
product_id INT NOT NULL COMMENT '商品ID',
amount DECIMAL(10, 2) NOT NULL COMMENT '金额',
status VARCHAR(20) NOT NULL COMMENT '状态',
created_at DATETIME NOT NULL COMMENT '创建时间'
) ENGINE=OLAP
UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 4
PROPERTIES ("replication_num" = "1");
-- 功能2:用户行为日志(明细模型 + 倒排索引)
CREATE TABLE IF NOT EXISTS user_events (
event_time DATETIME NOT NULL,
user_id INT NOT NULL,
event_type VARCHAR(50) NOT NULL,
page VARCHAR(200) NOT NULL,
extra VARIANT -- 半结构化字段
) ENGINE=OLAP
DUPLICATE KEY(event_time, user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 4
PROPERTIES (
"replication_num" = "1",
"inverted_index_storage_format" = "V2"
);
-- 倒排索引(支持全文检索)
CREATE INDEX idx_page ON user_events(page) USING INVERTED;
CREATE INDEX idx_event_type ON user_events(event_type) USING INVERTED;
-- 功能3:销售汇总(聚合模型)
CREATE TABLE IF NOT EXISTS sales_summary (
dt DATE NOT NULL,
category VARCHAR(50) NOT NULL,
total_amt DECIMAL(18,2) REPLACE,
order_cnt BIGINT SUM
) ENGINE=OLAP
AGGREGATE KEY(dt, category)
DISTRIBUTED BY HASH(dt) BUCKETS 4
PROPERTIES ("replication_num" = "1");
-- 功能4:物化视图(加速报表查询)
CREATE MATERIALIZED VIEW mv_daily_sales
AS SELECT
DATE(created_at) AS dt,
status,
COUNT(*) AS cnt,
SUM(amount) AS total
FROM orders
GROUP BY DATE(created_at), status;场景:电商订单管理后台,支持实时写入/更新订单,按条件查询。
// OrderController.java
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final JdbcTemplate jdbc;
/** 查询订单列表(支持状态过滤) */
@GetMapping
public List<Map<String, Object>> listOrders(
@RequestParam(defaultValue = "all") String status,
@RequestParam(defaultValue = "20") int limit) {
String sql = "all".equals(status)
? "SELECT * FROM demo.orders ORDER BY created_at DESC LIMIT ?"
: "SELECT * FROM demo.orders WHERE status = ? ORDER BY created_at DESC LIMIT ?";
return "all".equals(status)
? jdbc.queryForList(sql, limit)
: jdbc.queryForList(sql, status, limit);
}
/** 写入/更新订单(UNIQUE KEY 自动做 UPSERT) */
@PostMapping
public Map<String, Object> upsertOrder(@RequestBody Map<String, Object> order) {
String sql = "INSERT INTO demo.orders VALUES (?,?,?,?,?,NOW())";
int rows = jdbc.update(sql,
order.get("orderId"), order.get("userId"),
order.get("productId"), order.get("amount"), order.get("status"));
return Map.of("success", rows > 0, "msg", "UPSERT 成功");
}
/** 实时 GMV 聚合 */
@GetMapping("/gmv")
public Map<String, Object> gmv() {
String sql = "SELECT COUNT(*) AS cnt, SUM(amount) AS gmv "
+ "FROM demo.orders WHERE created_at >= CURDATE()";
return jdbc.queryForMap(sql);
}
}页面设计(Thymeleaf 片段,文件 order-dashboard.html):
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>订单实时看板</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"/>
</head>
<body class="container mt-4">
<h2>📦 订单实时看板</h2>
<!-- GMV 卡片 -->
<div class="row mb-4" id="gmvCards">
<div class="col-md-3">
<div class="card text-white bg-primary">
<div class="card-body">
<h5>今日 GMV</h5>
<h2 id="gmvVal">加载中...</h2>
</div>
</div>
</div>
</div>
<!-- 订单表格 -->
<table class="table table-striped" id="orderTable">
<thead><tr><th>订单ID</th><th>用户</th><th>金额</th><th>状态</th><th>时间</th></tr></thead>
<tbody id="orderBody"></tbody>
</table>
<script>
async function load() {
const r = await fetch('/api/orders');
const data = await r.json();
document.getElementById('orderBody').innerHTML =
data.map(o => `<tr><td>${o.order_id}</td><td>${o.user_id}</td>
<td>${o.amount}</td><td>${o.status}</td><td>${o.created_at}</td></tr>`).join('');
const g = await (await fetch('/api/orders/gmv')).json();
document.getElementById('gmvVal').textContent = '¥' + Number(g.gmv||0).toFixed(2);
}
load(); setInterval(load, 5000);
</script>
</body>
</html>场景:用户行为日志分析平台,支持关键词搜索、事件类型过滤。
// EventController.java
@RestController
@RequestMapping("/api/events")
@RequiredArgsConstructor
public class EventController {
private final JdbcTemplate jdbc;
/** 全文检索(利用 Doris 倒排索引) */
@GetMapping("/search")
public List<Map<String, Object>> search(
@RequestParam String keyword,
@RequestParam(defaultValue = "50") int limit) {
// MATCH_ANY 是 Doris 倒排索引全文检索语法
String sql = "SELECT event_time, user_id, event_type, page "
+ "FROM demo.user_events "
+ "WHERE page MATCH_ANY ? "
+ "ORDER BY event_time DESC LIMIT ?";
return jdbc.queryForList(sql, keyword, limit);
}
/** 批量写入事件(Stream Load 方式,高吞吐) */
@PostMapping("/batch")
public Map<String, Object> batchInsert(@RequestBody List<Map<String, Object>> events) {
String sql = "INSERT INTO demo.user_events VALUES (?,?,?,?,?)";
int[][] result = jdbc.batchUpdate(sql,
events, events.size(),
(ps, event) -> {
ps.setString(1, event.get("eventTime").toString());
ps.setInt(2, (Integer) event.get("userId"));
ps.setString(3, event.get("eventType").toString());
ps.setString(4, event.get("page").toString());
ps.setString(5, event.getOrDefault("extra", "{}").toString());
});
return Map.of("inserted", events.size());
}
/** 事件类型分布统计 */
@GetMapping("/stats")
public List<Map<String, Object>> stats() {
String sql = "SELECT event_type, COUNT(*) AS cnt "
+ "FROM demo.user_events "
+ "WHERE event_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR) "
+ "GROUP BY event_type ORDER BY cnt DESC";
return jdbc.queryForList(sql);
}
}场景:Flink/Kafka 替代方案,直接从应用端批量写入 CSV 数据。
// DorisStreamLoadService.java
@Service
public class DorisStreamLoadService {
@Value("${doris.fe-host}")
private String feHost;
private final OkHttpClient client = new OkHttpClient();
/**
* Stream Load:高吞吐批量写入
* 比 JDBC INSERT 快 5-10x,适合每批 1000+ 行
*/
public String streamLoad(String db, String table, String csvData) throws Exception {
String url = feHost + "/api/" + db + "/" + table + "/_stream_load";
// Base64 认证(root 无密码)
String credentials = Base64.getEncoder()
.encodeToString("root:".getBytes(StandardCharsets.UTF_8));
RequestBody body = RequestBody.create(
csvData.getBytes(StandardCharsets.UTF_8),
MediaType.parse("text/plain; charset=utf-8"));
Request request = new Request.Builder()
.url(url)
.addHeader("Authorization", "Basic " + credentials)
.addHeader("Expect", "100-continue")
.addHeader("label", "stream-load-" + System.currentTimeMillis())
.addHeader("format", "csv")
.addHeader("column_separator", ",")
.addHeader("columns", "order_id,user_id,product_id,amount,status,created_at")
.put(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
/** 生成测试数据并批量写入 */
public void loadTestData(int count) throws Exception {
StringBuilder sb = new StringBuilder();
Random rand = new Random();
String[] statuses = {"pending", "paid", "shipped", "done"};
for (int i = 1; i <= count; i++) {
sb.append(i).append(",")
.append(rand.nextInt(1000) + 1).append(",")
.append(rand.nextInt(100) + 1).append(",")
.append(String.format("%.2f", rand.nextDouble() * 1000)).append(",")
.append(statuses[rand.nextInt(statuses.length)]).append(",")
.append("2025-06-12 10:00:00").append("\n");
}
String result = streamLoad("demo", "orders", sb.toString());
System.out.println("Stream Load 结果: " + result);
}
}// ReportController.java
@RestController
@RequestMapping("/api/report")
@RequiredArgsConstructor
public class ReportController {
private final JdbcTemplate jdbc;
/**
* 查询会自动命中物化视图 mv_daily_sales
* 无需改 SQL,Doris 优化器透明改写
*/
@GetMapping("/daily")
public List<Map<String, Object>> dailyReport(
@RequestParam(defaultValue = "7") int days) {
String sql = "SELECT "
+ " DATE(created_at) AS dt, "
+ " status, "
+ " COUNT(*) AS cnt, "
+ " SUM(amount) AS total "
+ "FROM demo.orders "
+ "WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY) "
+ "GROUP BY DATE(created_at), status "
+ "ORDER BY dt DESC";
return jdbc.queryForList(sql, days);
}
/** 验证是否命中物化视图 */
@GetMapping("/explain")
public String explainQuery() {
String sql = "EXPLAIN SELECT DATE(created_at), status, COUNT(*), SUM(amount) "
+ "FROM demo.orders GROUP BY DATE(created_at), status";
List<Map<String, Object>> rows = jdbc.queryForList(sql);
return rows.stream()
.map(r -> r.values().iterator().next().toString())
.collect(Collectors.joining("\n"));
}
}-- 创建 MySQL 外部 Catalog(无需数据迁移直接查询)
CREATE CATALOG mysql_catalog PROPERTIES (
"type" = "jdbc",
"user" = "root",
"password" = "yourpwd",
"jdbc_url" = "jdbc:mysql://mysql-host:3306",
"driver_url" = "https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.33/mysql-connector-java-8.0.33.jar",
"driver_class" = "com.mysql.cj.jdbc.Driver"
);
-- 跨库联邦查询(Doris 本地 + MySQL 外表 JOIN)
SELECT o.order_id, o.amount, u.username, u.email
FROM demo.orders o
JOIN mysql_catalog.userdb.users u ON o.user_id = u.id
WHERE o.created_at >= CURDATE();居安思危,思则有备,有备无患。
生产环境最低配置:3 FE(1 Leader + 2 Follower)+ 3 BE。
# 假设三台机器:node1(10.0.0.1) node2(10.0.0.2) node3(10.0.0.3)
# ── Step 1:所有节点准备 ──
sudo sysctl -w vm.max_map_count=2000000
sudo sysctl -w vm.swappiness=0
echo 'vm.max_map_count=2000000' | sudo tee -a /etc/sysctl.conf
# ── Step 2:下载并解压(以 node1 为例)──
wget https://apache-doris-releases.oss-accelerate.aliyuncs.com/apache-doris-3.0.6-bin-x64.tar.gz
tar -zxvf apache-doris-3.0.6-bin-x64.tar.gz
# ── Step 3:配置 FE(fe/conf/fe.conf)──
echo 'meta_dir = /data/doris/fe-meta' >> fe/conf/fe.conf
echo 'priority_networks = 10.0.0.0/24' >> fe/conf/fe.conf
# ── Step 4:启动第一个 FE(Leader)──
bash fe/bin/start_fe.sh --daemon
# ── Step 5:注册其他 FE 为 Follower ──
mysql -uroot -P9030 -h10.0.0.1 \
-e "ALTER SYSTEM ADD FOLLOWER '10.0.0.2:9010'"
mysql -uroot -P9030 -h10.0.0.1 \
-e "ALTER SYSTEM ADD FOLLOWER '10.0.0.3:9010'"
# ── Step 6:在 node2/node3 启动 FE,指定 helper ──
bash fe/bin/start_fe.sh --helper 10.0.0.1:9010 --daemon
# ── Step 7:配置 BE(be/conf/be.conf)──
echo 'storage_root_path = /data/doris/be-storage' >> be/conf/be.conf
echo 'priority_networks = 10.0.0.0/24' >> be/conf/be.conf
# ── Step 8:注册并启动三个 BE ──
for host in 10.0.0.1 10.0.0.2 10.0.0.3; do
mysql -uroot -P9030 -h10.0.0.1 \
-e "ALTER SYSTEM ADD BACKEND '${host}:9050'"
done
# 在每台机器上执行
bash be/bin/start_be.sh --daemon
# ── Step 9:验证集群状态 ──
mysql -uroot -P9030 -h10.0.0.1 \
-e "SHOW PROC '/frontends'"
mysql -uroot -P9030 -h10.0.0.1 \
-e "SHOW PROC '/backends'"问题现象 | 原因 | 解决方法 |
|---|---|---|
BE 启动后 Alive=false |
| 在 be.conf 中设置 |
Stream Load 返回 401 | 密码认证失败 | 检查 Base64 编码, |
单机副本数报错 | 默认副本数 3,单节点只有 1 个 BE | 建表时加 |
OOM / BE 崩溃 | JVM/物理内存不足 | 设置 |
查询超时 | 未建合适索引 | 用 |
FE Leader 选举失败 | BDBJE 脑裂 | 确保 FE 数量为奇数(3 或 5),检查网络联通 |
Docker 容器无法启动 | CPU 不支持 AVX2 | 换用 |
Mac ARM 架构问题 | 镜像架构不匹配 | 使用 |
知己知彼,百战不殆。
对比维度 | Apache Doris 3.0 | ClickHouse 24.x | StarRocks 3.x |
|---|---|---|---|
架构 | MPP,FE+BE 模式 | 无主架构,ZK 协调 | MPP,FE+BE 模式 |
许可证 | Apache 2.0 全开源 | Apache 2.0 | BSL 1.1(核心企业版收费) |
多表 JOIN | ✅ 优秀 | ⚠️ 复杂 JOIN 性能差 | ✅ 优秀 |
实时 UPSERT | ✅ 主键模型 | ❌ 不支持原地更新 | ✅ 主键模型 |
湖仓支持 | ✅ 10+ 数据源 | ⚠️ 有限 | ✅ 主流数据源 |
易用性 | ✅ 开箱即用 | ⚠️ 需调优 | ⚠️ 企业版才完善 |
单查询极速 | 良好 | ✅ 极致 | 良好 |
社区活跃度 | ✅ Apache 顶级项目 | ✅ 活跃 | ⚠️ 商业驱动为主 |
向量检索 | 🔜 Roadmap | ⚠️ 实验性 | ⚠️ 有限支持 |
AI 友好度 | ✅ GenAI 数据底座战略 | 一般 | 一般 |
选型结论:追求极致单表聚合速度且数据模型简单 → ClickHouse 需要高性能+愿意付商业费用 → StarRocks 企业版 大多数互联网企业,需要低成本运营+全功能+社区支持 → Apache Doris
# Ubuntu 22.04
curl https://clickhouse.com/ | sh
./clickhouse install
sudo clickhouse start
clickhouse-client # 进入 CLIdocker run -d --name starrocks \
-p 9030:9030 -p 8030:8030 -p 8040:8040 \
starrocks/allin1-ubuntu:3.3-latest
# 等待约 30 秒后
mysql -P9030 -h127.0.0.1 -uroot --prompt="StarRocks> "会当凌绝顶,一览众山小。

场景一:代码质量分析平台(AICoding 支撑)
-- 存储 AI 代码审查日志
CREATE TABLE code_review_log (
review_id BIGINT NOT NULL,
repo VARCHAR(200),
file_path VARCHAR(500),
language VARCHAR(50),
issue_type VARCHAR(100),
ai_model VARCHAR(50),
score FLOAT,
suggestion TEXT, -- AI 生成的建议
reviewed_at DATETIME
) ENGINE=OLAP
UNIQUE KEY(review_id)
DISTRIBUTED BY HASH(review_id) BUCKETS 8
PROPERTIES ("replication_num" = "1");
-- 实时查询:哪些文件 Bug 最多?
SELECT file_path, COUNT(*) AS issue_cnt, AVG(score) AS avg_score
FROM code_review_log
WHERE reviewed_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY file_path
ORDER BY issue_cnt DESC
LIMIT 20;场景二:RAG 知识库 + Doris 结构化检索混合
# 伪代码:LangChain + Doris 混合检索
def hybrid_search(user_question: str) -> str:
# 1. 向量数据库(Chroma/Milvus)做语义检索
semantic_docs = vector_store.similarity_search(user_question, k=5)
# 2. Doris 做结构化精确过滤(补充向量检索盲区)
sql = """
SELECT content, source, updated_at
FROM knowledge_base
WHERE content MATCH_ANY %s
AND category = 'technical'
ORDER BY updated_at DESC
LIMIT 10
"""
doris_docs = doris_client.execute(sql, [user_question])
# 3. 合并上下文送给 LLM
context = semantic_docs + doris_docs
return llm.chat(f"基于以下资料回答问题: {context}\n问题: {user_question}")基于老李的实践,以下工具组合在 AI 数据工程中效果最佳:
工具 | 推荐理由 |
|---|---|
Claude Code | 直接生成 SQL DDL、Spring Boot 集成代码;理解 Doris 特有语法(MATCH_ANY、物化视图)比通用 IDE 插件更准确 |
Claude in Chrome | 在 Doris FE Web UI 页面直接分析 Query Profile,识别慢查询瓶颈,生成优化建议 |
Claude in Excel | 导出 Doris 查询结果到 Excel 后,用 AI 自动生成数据分析报告 |
选 Claude Code 的核心理由:Doris 的 SQL 方言(VARIANT 类型、Inverted Index 语法、物化视图改写规则)与标准 ANSI SQL 有差异,Claude Code 可以基于上下文生成符合 Doris 特性的生产级 SQL,而不是"看起来对但跑不了"的模板代码。

老李的教训:数仓报表慢不是 Doris 的锅,是没建物化视图的锅。每个高频报表查询都应该有对应的异步物化视图,这不是优化手段,是标准设计范式。

存算分离不只是架构时髦词,它意味着:存储成本降低 60-80%,计算节点可以无状态水平扩展。对于云上部署的团队,这是选 Doris 3.0 的决定性理由。

Doris 2025 Roadmap 明确提出"面向 AI 时代的数据基础设施"。向量检索内置、自然语言查询、AI 辅助优化——这不是 PPT,而是 SelectDB 已经在企业版中落地的功能。开源版的跟进只是时间问题。
博学之,审问之,慎思之,明辨之,笃行之。

决策点 | 推荐做法 | 反面教材 |
|---|---|---|
副本数 | 单机开发设 1,生产至少 3 | 单机跑 3 副本,BE 启动失败排查半天 |
数据模型 | 有更新需求用 UNIQUE KEY | 用 DUPLICATE 存订单,UPDATE 语句执行失败 |
写入方式 | 批量 >1000 行用 Stream Load | 循环单条 INSERT,吞吐量 1/10 |
查询加速 | 先 EXPLAIN 再建索引 | 凭感觉建倒排索引,覆盖度 0 |
物化视图 | 核心报表必须配物化视图 | 查询慢了才想起来优化 |
高可用 | FE 奇数节点,至少 3 | 2 个 FE 脑裂,集群停服 |
开源协议 | 直接用 Apache 版 | 混用 SelectDB 企业版功能后被商务找上门 |
行动号召:别再让数据工程师深夜写数据同步脚本了。把 Doris 跑起来,用 Claude Code 生成你的第一张物化视图,用 Stream Load 验证吞吐上限,然后告诉老板:我们的数据架构,终于可以支撑 AI 时代的增长了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。