编程 DuckDB 深度实战:当「分析界的 SQLite」装上向量化引擎——1.5 版本如何用进程内 OLAP 重写本地数据心智模型

2026-07-15 03:12:30 +0800 CST views 8

DuckDB 深度实战:当「分析界的 SQLite」装上向量化引擎——1.5 版本如何用进程内 OLAP 重写本地数据心智模型

关键词:进程内数据库、向量化执行、列存、OLAP、Parquet、VARIANT、HTAP
适用读者:写过 SQL、被 pandas 内存卡过脖子、又不想为一坨临时分析去搭一套 Spark/ClickHouse 的工程派程序员。

0. 引子:你真的需要一个数据库服务器吗?

先问一个扎心的问题。

你手里有一份 12 GB 的 CSV(某业务导出的订单明细),老板让你「算一下最近三个月每个城市的客单价和复购率」。你会怎么做?

  • 方案 A:pandas 一把梭。 pd.read_csv(...) 之后内存直接爆掉,因为 CSV 读进来要物化成对象,12 GB 文本 often 膨胀到 40 GB+ 内存。于是你加 chunksize、加类型转换、加分块聚合,写了一屏幕代码,跑了 8 分钟,还不确定对不对。
  • 方案 B:导进 PostgreSQL。 你先 CREATE TABLE、写 COPY、等它建索引,发现字段类型又对不上,改了三遍 schema,半小时过去了,才开始写第一条 GROUP BY
  • 方案 C:上 Spark。 环境都搭不起来,local 模式也吃内存,杀鸡用牛刀。

这三种方案的共性痛点:为了「问一个问题」,你先花 80% 的精力在「把数据搬进一个能问问题的地方」。数据不在数据库里,你的分析能力就被锁死了。

DuckDB 想解决的就是这件事。它的核心哲学只有一句话:让分析引擎直接跑到数据待着的地方,而不是把数据搬到引擎里。 数据在本地 CSV?直接查。在 S3 上的 Parquet?直接查。在 pandas DataFrame / Arrow Table 里?直接查。在另一个数据库的只读副本上?也能查。

它常被叫做「分析界的 SQLite」——但这是个容易误导的称号。SQLite 是 OLTP 王者(行存、事务、点查),DuckDB 是 OLAP 利器(列存、向量化、扫描聚合)。两者共享的是「嵌入式、零运维、单文件」的工程体验,而不是使用场景。

本文从工程师视角,把 DuckDB 1.5(代号 Variegata,2026 年当前稳定线 1.5.x)拆开讲透:它为什么快、内部怎么跑、怎么落地到真实数据管线,以及 1.5 带来的 VARIANT / GEOMETRY / 全新 CLI 到底解决了什么。全程配可运行代码。


1. 核心概念:进程内 OLAP 到底意味着什么

1.1 三个关键词

理解 DuckDB,先抓住三个词:In-Process(进程内)Columnar(列存)Vectorized(向量化)

In-Process。 DuckDB 不是一个独立服务,它不是 mysqld、不是 clickhouse-server。它是一段被链接进你进程的 C++ 库。你 import duckdb 之后,查询引擎和你的 Python 解释器、你的 Rust main、你的 Node 事件循环,跑在同一个地址空间里。没有 socket、没有序列化、没有网络往返、没有连接池管理。调用一次查询,函数就返回结果。这是它「零配置即用」的根本原因——没有 daemon 要起,没有端口要开。

Columnar。 传统行存数据库(SQLite、MySQL)按行把记录连续存放:(id=1, name="a", price=10) (id=2, name="b", price=20) ...。分析查询通常只关心少数几列(比如「算 price 的平均值」),行存被迫把整行都读进内存再丢掉无关的 name 列。列存反过来,把同一列的值连续存放:price: [10, 20, 30, ...]name: ["a","b",...]。聚合时只读 price 这一块连续内存,CPU 缓存命中率极高,磁盘 I/O 也只拉需要的列。这是 OLAP 性能的第一性原理。

Vectorized。 这是 DuckDB 和普通列存最大的分水岭。很多数据库一次处理一行(tuple-at-a-time),每行都要走一遍解释器、一次函数调用、一次虚表分发,上亿行就是上亿次函数调用,分支预测全崩。DuckDB 一次处理一向量(vector,默认 2048 行)。一个 SUM 算子拿到的是 2048 个 price 值的连续数组,用一个紧凑循环一次性算完。配合现代 CPU 的 SIMD 指令,一次能并行处理多个值。函数调用开销从「每行一次」降到「每 2048 行一次」。

把这三件事叠起来,你就得到了 DuckDB 的体感:一个 pip install duckdb,打开一个几 GB 的 Parquet,一句 SQL,几百毫秒出结果,内存占用却很克制。

1.2 它和 SQLite / pandas / Spark 的边界在哪

维度SQLitepandasDuckDBSpark
定位嵌入式 OLTP内存数据框嵌入式 OLAP分布式 OLAP
存储行存内存对象列存(向量化)列存(分布式)
数据规模MB~GB受内存限制GB~TB(溢出磁盘)TB~PB
是否服务否(库)否(库)否(库)是(集群)
并发写弱(单写者)
典型场景应用内嵌数据库小数据探索本地/嵌入式分析超大规模 ETL

一句话总结边界:DuckDB 吃的是「单机、分析为主、数据在文件/对象存储里」这一大块场景。它是 pandas 在「数据比内存大」时的升级替代,是 SQLite 在「我要做聚合而非点查」时的升级替代,是 Spark 在「我其实只有一台机器」时的降级替代(但体感往往更好)。

1.3 1.5 版本带来的关键新概念:VARIANT

DuckDB 1.5 最值得工程师兴奋的特性,是 VARIANT 类型

做数据分析的人都知道「半结构化数据」有多烦:一份 API 返回的 JSON,90% 的字段大家都有,但总有 10% 的字段在乱飘——有的记录多一个 metadata,有的记录 tags 是数组有的却是字符串。传统做法是用 DuckDB 的 JSON 类型把整列当文本存,查询时现解析。灵活,但慢:每次取 data->>'price' 都要重新解析整段 JSON 文本。

VARIANT 的思路是:在写入时就把 JSON「分解」(shred)到底层列里。结构稳定的部分(如 priceuser_id)落到各自类型化的列,真正不规则的尾巴才兜底存为变体。查询时你只取 price,磁盘上只读那一段定长数值列,不用碰其它字段,类型也已经是 DOUBLE 而不是 VARCHAR。官方内部基准里,对半结构化 JSON 的查询有 10~100 倍提速。

在 AI 时代这点尤其关键:模型吐出的 tool call、embedding 元数据、可观测性日志,全是「大体同构又不完全一致」的 JSON。VARIANT 让 DuckDB 在处理这类数据时既保灵活又保速度。

-- 直接把 JSON 文件按 VARIANT 读进来
CREATE TABLE events AS
SELECT * FROM read_json('events/*.json', format='variant');

-- 之后查询像普通列一样快,类型已自动推断
SELECT user_id, payload['action'] AS action, COUNT(*)
FROM events
WHERE event_time > DATE '2026-07-01'
GROUP BY 1, 2;

2. 架构分析:一条 SQL 在 DuckDB 里是怎么跑完的

光知道「它快」不够,工程师要懂「为什么快、什么时候会慢」。这一节拆执行引擎。

2.1 Pull 还是 Push?DuckDB 的向量化 Pipeline

经典 Volcano 模型(几乎每个数据库教科书都讲)是 pull(迭代器)模型:顶层算子调 child.Next() 拉一行,自顶向下递归。每行一次虚函数调用,解释器开销巨大,且一行一行处理没法用 SIMD。

DuckDB 用的是 push-based 的向量化流水线(vectorized pipeline)。执行计划被切成一条条 pipeline,每个 pipeline 由一串算子组成;数据以 2048 行的向量(Data Chunk)为单位,自底向上地在一个紧凑循环里流过整条 pipeline,直到某个阻塞算子(如 ORDER BYHASH JOIN 的 build 端)需要攒够数据才往下传。

一个算子的生命周期被显式拆成几个阶段,这是理解 DuckDB 并行与溢出机制的钥匙:

  • Sink:线程本地地接收上游推来的数据块,攒进自己的局部状态。
  • Combine:线程完成接收后,把自己的局部状态合并。
  • Finalize:所有线程都 Combine 完,调用一次,做全局收尾。
  • GetData:把最终结果吐给下游。

为什么这么设计?因为它天然适配多线程并行磁盘溢出(spilling)。每个 pipeline 的上游可以被多个线程同时 Sink(每个线程处理不同的文件分片或不同的行组),局部状态互不干扰,最后再 Combine。而当内存不够时,Sink 阶段可以把攒到一半的状态序列化到磁盘临时文件,需要时再读回来——这就是 DuckDB 能处理「比内存大得多」的数据的秘密。

2.2 存储层:列存、压缩与 Parquet 原生读取

DuckDB 自己的持久化格式是单文件 .duckdb,内部按 Row Group(默认约 12 万行)切块,每个 Row Group 内按列存储,并对每列自动选压缩编码(字典编码、RLE、位压缩、常量编码等)。

但更妙的是:DuckDB 不需要把数据「导入」自己,它原生能读外部格式。对 Parquet 尤其如此——Parquet 本身就是列存 + 压缩 + 统计信息(min/max/null_count per row group)。DuckDB 读 Parquet 时:

  1. 先看文件 footer 里的 schema 和每列的统计信息;
  2. 谓词下推(predicate pushdown)WHERE dt = '2026-07-01' 直接跳过不包含该日期的 Row Group;
  3. 投影下推(projection pushdown)SELECT price 只解压 price 那一列;
  4. 还可以做 Late Materialization:先按过滤条件定位行号,再回查需要的列。

这意味着你 50 GB 的 Parquet 放在 S3 上,DuckDB 可能只拉了其中 200 MB 就回答了你的查询。对分析师来说,这比「先全量下载再过滤」快几个数量级。

-- 直接查 S3 上的 Parquet,无需先下载
SELECT region, SUM(amount) AS revenue
FROM read_parquet('s3://my-bucket/sales/2026/*.parquet')
WHERE dt BETWEEN DATE '2026-07-01' AND DATE '2026-07-14'
GROUP BY region;

注:read_parquet 配合 httpfs 扩展即可直连 S3 / GCS / R2,凭证走环境变量或 SET 配置,无需把对象拉到本地。

2.3 查询直接打在文件上 vs 导入:怎么选

DuckDB 两种用法都支持,区别在工程权衡:

  • 「无导入」查询(zero-copy 分析):数据本就在 Parquet/CSV/JSON 文件或对象存储里,你只是想问问题。直接用 read_parquet / read_csv_auto。零 ETL、零存储膨胀,适合探索性分析、一次性报表。
  • 「导入」持久化:数据要反复查、要建索引/约束、要被多个 session 共享、要 ACID。这时 CREATE TABLE ... AS SELECT ... FROM read_parquet(...) 把数据物化进 .duckdb 文件。DuckDB 的压缩列存通常比原始 CSV 还小。

经验法则:探索用前者,固化为服务用后者。 很多生产管线是「每天把 S3 上的增量 Parquet INSERT 进一张持久表」,兼顾新鲜度和查询性能。

2.4 扩展系统:引擎是内核,能力靠插件

DuckDB 内核保持精简,能力靠 扩展(extensions) 热加载。需要时才 INSTALL + LOAD

  • httpfs:连 S3/GCS 等对象存储
  • json / 新版 VARIANT:半结构化数据
  • spatial:GEOMETRY 空间类型(1.5 内置了 GEOMETRY 类型,spatial 扩展负责函数)
  • fts:全文检索
  • parquet / iceberg / delta:湖仓格式
  • postgres_scanner / mysql:直接扫远端数据库的表

扩展是社区生态的护城河。2026 年 DuckDB 的社区扩展体系已经很成熟,第三方可以发布自己的扩展(比如做加密、做特定格式解析),这在嵌入式数据库里很少见。


3. 代码实战:从「能跑」到「跑进生产」

光讲架构太虚,下面全是能直接抄的代码。

3.1 Python 五分钟上手

pip install duckdb
import duckdb

# 1) 一行 SQL 直接查 Parquet,结果给 pandas
df = duckdb.sql("""
    SELECT region, COUNT(*) AS orders, SUM(amount) AS gmv
    FROM read_parquet('s3://my-bucket/sales/2026/*.parquet')
    WHERE dt >= DATE '2026-07-01'
    GROUP BY region
    ORDER BY gmv DESC
""").df()
print(df)

# 2) 和 pandas / pyarrow 零拷贝互操作
import pandas as pd
orders = pd.DataFrame({"id": [1, 2, 3], "amount": [10.0, 25.5, 8.0]})
# pandas -> DuckDB(零拷贝视图,不复制内存)
rel = duckdb.sql("SELECT id, amount, amount * 1.13 AS amount_with_tax FROM orders")
print(rel)

# 3) 持久化到单文件数据库
con = duckdb.connect('analytics.duckdb')
con.execute("CREATE TABLE IF NOT EXISTS daily_sales AS SELECT * FROM read_parquet('s3://my-bucket/sales/2026/*.parquet') WHERE 1=0")
con.execute("INSERT INTO daily_sales SELECT * FROM read_parquet('s3://my-bucket/sales/2026/2026-07-14.parquet')")
con.close()

关键点:DuckDB 跟 pandas/Arrow 是零拷贝桥梁。你可以把 pandas 当「输入」,DuckDB 做重活(大表 join、分组聚合),结果再吐回 pandas 画图。既享受 pandas 的生态,又不被它的内存模型卡死。

3.2 用 DuckDB 重写一段「pandas 爆内存」的 ETL

假设你要算「每个用户的首单渠道和累计消费」,原始数据是 30 GB 的订单 CSV。

pandas 思路(会爆):

# ❌ 30GB CSV 全读进内存,大概率 OOM
df = pd.read_csv('orders.csv')
first = df.sort_values('ts').groupby('user_id').first()[['channel']]
agg = df.groupby('user_id')['amount'].sum()

DuckDB 思路(流式、溢出磁盘):

import duckdb
con = duckdb.connect()
# 直接读 CSV,DuckDB 自动推断 schema、并行扫描、内存不够就溢写磁盘
con.execute("""
    CREATE TABLE orders AS
    SELECT * FROM read_csv_auto('orders.csv', sample_size=-1);
""")

first_channel = con.sql("""
    SELECT user_id, channel AS first_channel
    FROM (SELECT user_id, channel,
                 ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts) AS rn
          FROM orders)
    WHERE rn = 1
""").df()

total = con.sql("""
    SELECT user_id, SUM(amount) AS lifetime_amount
    FROM orders GROUP BY user_id
""").df()

result = first_channel.merge(total, on='user_id')

区别在于:DuckDB 的 read_csv_auto流式+并行的,不会把 30 GB 一次性物化成 Python 对象;聚合过程走向量化引擎,内存吃紧时自动 spill 到临时文件。你写的是标准 SQL,跑的是接近 C++ 的性能。

3.3 全新 CLI:1.5 的动态提示符与高亮

DuckDB 1.5 重做了命令行客户端。最实用的是动态提示符——默认显示当前连接的数据库和 schema,再也不会「我到底在哪个库」:

memory D ATTACH 'my_database.duckdb';
memory D USE my_database;
my_database D CREATE SCHEMA my_schema;
my_database.my_schema D SELECT 1;

还带来:暗/亮色统一高亮(关键字、字符串、数字、函数各有配色,且可 .highlight_colors 自定义)、内置分页器、更聪明的 .tablesDESCRIBE。对天天泡在终端的分析师,这是「顺手」级别的提升。

-- 一键看表结构
DESCRIBE ducks;
-- ┌──────────────┬─────────┐
-- │    column    │  type   │
-- ┌──────────────┼─────────┤
-- │ id           │ integer │
-- │ name         │ varchar │
-- │ extinct_year │ integer │
-- └──────────────┴─────────┘

3.4 Rust 里嵌一个分析引擎

DuckDB 官方提供 Rust binding(duckdb crate),适合把分析能力编进你的系统程序。

use duckdb::{Connection, Result};

fn main() -> Result<()> {
    let conn = Connection::open_in_memory()?;

    conn.execute_batch(
        "CREATE TABLE metrics(ts TIMESTAMP, labels JSON, value DOUBLE);",
    )?;

    // 用 VARIANT 思路:JSON 列直接当半结构化存
    conn.execute(
        "INSERT INTO metrics VALUES (NOW(), '{\"host\":\"web-01\",\"region\":\"sh\"}', 0.93)",
        [],
    )?;

    let mut stmt = conn.prepare(
        "SELECT labels->>'region' AS region, AVG(value) AS avg_load
         FROM metrics GROUP BY 1",
    )?;
    let rows = stmt.query_map([], |row| {
        Ok((row.get::<_, String>(0)?, row.get::<_, f64>(1)?))
    })?;
    for r in rows {
        println!("{:?}", r?);
    }
    Ok(())
}

在嵌入式场景(CLI 工具、边缘设备、WASM 插件)里塞一个 DuckDB,相当于白送了一套带 SQL 的分析能力,还不用起服务。

3.5 HTAP 趋势:把 MySQL 的只读副本变成 DuckDB

一个 2026 年很实在的用法:用 DuckDB 给 OLTP 数据库做分析加速。云厂商(如阿里云 RDS MySQL)已经推出「DuckDB 分析只读实例」——把 MySQL 高可用主实例挂载一个 DuckDB 只读节点,慢 SQL / 复杂报表直接打到 DuckDB 侧,主库不被分析查询拖垮,实现一体化 HTAP。

自己也能低成本复刻这个思路:用 mysql 扩展把 MySQL 的表映射成 DuckDB 的外部表,复杂聚合在 DuckDB 侧跑,点查仍走 MySQL。

-- 安装 mysql  scanner 扩展
INSTALL mysql; LOAD mysql;
-- 把远端 MySQL 的表映射进来(只读)
ATTACH 'host=10.0.0.5 user=ro password=xxx database=shop' AS mysql_db (TYPE mysql);
-- 复杂报表在 DuckDB 侧向量化执行,不压主库
SELECT o.city, SUM(oi.price * oi.qty) AS gmv
FROM mysql_db.orders o
JOIN mysql_db.order_items oi ON oi.order_id = o.id
WHERE o.created_at >= NOW() - INTERVAL 30 DAY
GROUP BY o.city
ORDER BY gmv DESC;

4. 性能优化:把 DuckDB 跑满的工程要点

「快」不是白送的。下面几条决定你的查询是 200 ms 还是 20 s。

4.1 永远让数据保持「列存 + 统计信息」

CSV 是分析的最差输入:没有列存、没有统计信息、没有压缩、类型还要猜。能用 Parquet 就别用 CSV。

-- 把 CSV 落盘成压缩 Parquet,后续查询起飞
COPY (SELECT * FROM read_csv_auto('raw/orders.csv'))
TO 'clean/orders.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);

Parquet 的 per-row-group 统计信息让 DuckDB 能做谓词下推,直接跳过无关数据。这一条往往带来数量级差异。

4.2 用对 1.4 / 1.5 的排序与 JSON 优化

DuckDB 1.4.0 重写了排序算法,借鉴了 hash join / hash agg 里「可溢出分页布局」的思路,整体有 3~10 倍提升(取决于是否超内存、是否能边读边排)。所以对大结果集 ORDER BY,升级到 1.5.x 直接白嫖性能。

对 JSON 半结构化数据,如前所述,用 VARIANT 而非老 JSON 类型,把「每次查询现解析文本」变成「写入时分解、查询时直接读列」。10~100 倍的差距主要来自这里。

4.3 控制内存与并行

DuckDB 默认会用满机器内存和所有核。在共享环境(比如和 Web 服务同机)要收着点:

-- 限制内存上限(溢出到磁盘,避免 OOM 杀进程)
SET memory_limit = '4GB';
-- 限制并行线程数
SET threads = 4;

EXPLAIN / EXPLAIN ANALYZE 是你的好朋友,能看执行计划和真实耗时分布:

EXPLAIN ANALYZE
SELECT region, SUM(amount) FROM read_parquet('s3://bucket/sales/*.parquet')
GROUP BY region;

如果看到某个 HASH JOINORDER BY 占了 90% 时间且发生了 spill,说明要么数据倾斜,要么该加过滤条件减少输入。

4.4 利用「友好 SQL」少写样板

DuckDB 有一些贴心语法糖,既减少出错也往往被优化器更好处理:

-- GROUP BY ALL:按 SELECT 里所有非聚合列分组,改 SELECT 不用同步改 GROUP BY
SELECT region, city, SUM(amount) FROM sales GROUP BY ALL;

-- PIVOT:一行把行转成列,替代手写一堆 CASE WHEN
PIVOT sales ON region USING SUM(amount);

-- AsOf JOIN:按「最接近且不超越」的时间点对齐(时序/行情数据神器)
SELECT t.ts, t.price, q.quote
FROM ticks t ASOF JOIN quotes q ON t.ts >= q.ts;

这些不是噱头——GROUP BY ALL 减少重构错误,AsOf JOIN 用专门的合并算法比 t.ts >= q.ts 的常规 join 高效得多。

4.5 什么时候 DuckDB 不是答案

工程师的成熟,是知道自己手里的锤子不适合哪些钉子。DuckDB 明确不适合:

  • 高并发 OLTP / 多写者:它是单写者模型,每秒上万次点写不是它的活。
  • 需要分布式超大规模(PB 级跨节点):它单机。真到那份上,ClickHouse / Spark / 数据湖引擎更合适。
  • 强一致事务 + 多应用共享写:它偏分析,事务能力够用但不如 PG/MySQL 那套成熟。

正确姿势是组合:OLTP 用 PG/MySQL 扛写,分析用 DuckDB(或它的只读副本)扛读,中间靠扩展或增量同步打通。这正是 HTAP 的思路。


5. 总结与展望:DuckDB 在 2026 的位置

把视角拉远一点。DuckDB 的崛起,本质反映了数据基础设施的一个转向:分析能力的「下沉」。过去分析要在中央集群(Spark/Hive)里做,数据要「上云集中」;现在,强大的向量化引擎可以嵌进任何进程、任何边缘、任何笔记本,数据在哪分析就在哪。这种「本地优先(local-first)」的分析范式,和 SQLite 当年把事务能力下沉到应用里,是同一个故事的现代重演。

2026 年 DuckDB 生态的几个信号值得关注:

  1. MotherDuck 与「DuckLake」:云端托管 + 本地引擎的混合架构,让「本地算、云上协作、按需上推」成为常态。数据不必二选一地在本地或云端,而是按需流动。
  2. VARIANT 把 AI 数据收编:tool call、embedding 元数据、agent 日志全是半结构化 JSON,VARIANT 让 DuckDB 成为 AI 数据管线的天然底座。
  3. 社区扩展繁荣:从加密到特定格式解析,第三方扩展让这个嵌入式引擎越来越像一个「可裁剪的分析操作系统」。
  4. 与湖仓格式打通:Iceberg / Delta 读取能力,让 DuckDB 能直接坐到数据湖上面做「轻量查询层」,替代一部分 Trino/Presto 的场景。

给工程师的落地建议(按成熟度排序):

  • 今天就能用:本地数据分析、pandas ETL 升级、CSV/Parquet 探索、CLI 即席查询。
  • 近期可用:给 OLTP 库挂 DuckDB 只读分析副本(HTAP)、S3 上直接跑报表。
  • 值得关注:DuckLake / MotherDuck 混合架构、AI 半结构化数据的 VARIANT 底座。

最后回到开头那个 12 GB CSV 的问题。在 2026 年,正确的答案可能只是:

import duckdb
print(duckdb.sql("""
    SELECT city,
           AVG(amount)                                   AS arpu,
           COUNT(DISTINCT user_id) FILTER (
               WHERE rn = 1) / COUNT(DISTINCT user_id)   AS repurchase_rate
    FROM (
        SELECT city, user_id, amount,
               ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts) AS rn
        FROM read_csv_auto('orders.csv')
    )
    WHERE ts >= NOW() - INTERVAL 90 DAY
    GROUP BY city
""").df())

没有服务器、没有建表、没有等导入。一句 SQL 直接打在文件上,几百毫秒出结果。这就是进程内 OLAP 重写本地数据心智模型的含义:你问问题的方式,从此不必再为「怎么把数据搬进来」而分心。

DuckDB 不是要取代谁。它只是把「分析」这件本该很轻的事,重新变轻了。


参考与延伸:DuckDB 官方文档与工程博客、《DuckDB 1.5.0 发布公告》(代号 Variegata,引入 VARIANT 与 GEOMETRY 类型、重做 CLI)、DuckDB 1.4.0 排序算法重写技术博客、MotherDuck 对 1.5 特性的工程解读,以及阿里云 RDS「DuckDB 分析只读实例」HTAP 实践。

推荐文章

Vue3 实现页面上下滑动方案
2025-06-28 17:07:57 +0800 CST
ElasticSearch集群搭建指南
2024-11-19 02:31:21 +0800 CST
H5端向App端通信(Uniapp 必会)
2025-02-20 10:32:26 +0800 CST
程序员茄子在线接单