跳至主要内容

ClickHouse与MySQL的区别对比指南

ClickHouse 与 MySQL 对比指南

—— 写给有 MySQL 经验的用户


一、先说结论:两者根本不是同一类数据库

理解 ClickHouse 最容易踩的坑,就是把它当成"性能更强的 MySQL"。实际上两者的设计目标完全不同:

维度 MySQL ClickHouse
定位 OLTP(联机事务处理) OLAP(联机分析处理)
典型场景 下单、支付、用户信息增删改查 日志分析、报表、BI、时序数据、用户行为分析
存储方式 行式存储(row-based) 列式存储(column-based)
单次查询数据量 少量行,高频次 大量行,低频次但计算量大
事务 完整 ACID 事务 不支持传统事务,写入近似"追加"
单条数据修改 高效(UPDATE/DELETE 直接改) 低效,官方不推荐频繁做

一句话理解:MySQL 擅长"找到某一行/几行数据并精确修改",ClickHouse 擅长"扫描亿万行数据做聚合计算"。


二、使用方式与功能对比

2.1 建表语法差异

MySQL:

CREATE TABLE orders (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT,
    amount DECIMAL(10,2),
    created_at DATETIME
) ENGINE=InnoDB;

ClickHouse:

CREATE TABLE orders (
    id UInt64,
    user_id UInt64,
    amount Decimal(10,2),
    created_at DateTime
) ENGINE = MergeTree()
ORDER BY (user_id, created_at)
PARTITION BY toYYYYMM(created_at);

几个关键差异点:

  • 没有 AUTO_INCREMENT:ClickHouse 没有自增主键的概念,通常用雪花算法、UUID 或业务自带的唯一键。
  • ENGINE 不是可选项,而是核心:MySQL 里 ENGINE=InnoDB 基本是默认项,很少有人关心;ClickHouse 里选什么 Engine(MergeTree 及其变体)直接决定了这张表的行为,是建表时必须认真考虑的问题。
  • ORDER BY 不是排序子句,而是索引定义:这是最容易误解的地方,后面架构部分会详细讲。
  • PARTITION BY 是物理分区:常按时间分区,这点和 MySQL 的分区表概念类似,但在 ClickHouse 里几乎是标配。

2.2 增删改查的差异

操作 MySQL ClickHouse
INSERT 单行/小批量高频写入,性能良好 应尽量大批量写入(几千到几十万行一批),频繁单行 INSERT 会产生大量小文件,拖垮性能
UPDATE 原地修改,同步生效,行级锁 通过 ALTER TABLE ... UPDATE 实现,本质是异步的"变更任务"(mutation),不是立即生效,代价很高,官方不建议高频使用
DELETE 同上,同步生效 同 UPDATE,是异步 mutation;也可用分区删除(DROP PARTITION)做批量高效删除
SELECT 依赖索引定位少量行 依赖列裁剪 + 稀疏索引跳过数据块,擅长全表扫描式的聚合查询
JOIN 成熟,优化器会自动选择驱动表 支持但较弱,官方建议把小表放在右边(RIGHT 表会被载入内存做 Hash Join),大表 JOIN 大表要谨慎
事务 完整支持(InnoDB) 仅在单个 INSERT 内保证原子性,不支持跨语句事务
外键约束 支持 不支持,需要业务层自己保证一致性

2.3 索引使用习惯的转变

MySQL 用户往往有很强的"加索引"思维——哪个字段查得慢就给它加索引。这个思维在 ClickHouse 里需要调整:

  • ClickHouse 的主键(ORDER BY只能有一套,且建表后基本不能轻易改,需要在建表前就想清楚最常用的查询过滤条件是什么。
  • 没有类似 MySQL 那种"任意字段建二级索引"的能力,取而代之的是跳数索引(skip index),例如 minmaxsetbloom_filter,用于加速非主键字段的过滤,但效果和 B+树索引不是一回事,更像是"帮助跳过明显不相关的数据块"。
  • 物化视图(Materialized View)在 ClickHouse 里承担了 MySQL 中"预计算/汇总表"的角色,是很常用的性能优化手段。

2.4 适用场景总结

  • 继续用 MySQL:订单系统、账户余额、库存扣减等需要强一致性、行级精确修改的业务。
  • 引入 ClickHouse:埋点日志分析、监控指标、用户行为分析、报表看板、需要对海量历史数据做 GROUP BY/COUNT/SUM 的场景。
  • 常见组合:业务数据仍存 MySQL,通过 CDC(如 Canal、Debezium)或定时同步的方式把数据导入 ClickHouse 做分析,两者并存而不是相互替代。

三、架构对比

3.1 MySQL 架构回顾

客户端
  
Server 层:连接管理 / SQL 解析 / 优化器 / 执行器
  
存储引擎层:InnoDB(B+树、Buffer Pool、Redo Log、Undo Log)
  
磁盘:.ibd 数据文件

MySQL 的核心设计是单机为主,主从复制为辅。一个主库承担全部写入,通过 binlog 异步/半同步复制到从库,从库主要承担读流量分担,扩展性主要靠"垂直加配置"或"业务层分库分表"。

3.2 ClickHouse 架构

客户端
  
Distributed 表引擎(可选,负责把查询路由到各个分片)
  
分片 1(Shard)          分片 2(Shard)        分片 N
 ├─ 副本 A(Replica)      ├─ 副本 A              ├─ 副本 A
 └─ 副本 B(Replica)      └─ 副本 B              └─ 副本 B
  
ClickHouse Keeper / ZooKeeper(协调副本间数据同步)

关键概念对照:

MySQL 概念 ClickHouse 概念 说明
主从复制 副本(Replica) 副本之间地位对等,都可读写(配合 ReplicatedMergeTree),不是主从关系
分库分表(业务层实现) 分片(Shard) ClickHouse 原生支持分片,通过 Distributed 表引擎自动把查询分发到各分片再汇总结果
无直接对应概念 ClickHouse Keeper 类似 MySQL 复制中"谁是主库"的协调作用,但用于协调多副本间的数据一致性,早期版本依赖 ZooKeeper
单机纵向扩展为主 天然支持横向扩展(Scale-out) 加机器、加分片是 ClickHouse 应对数据量增长的标准手段

一个直观的理解:MySQL 的复制是"为了高可用和读扩展",ClickHouse 的分片+副本是"分片负责横向扩展算力和存储,副本负责高可用",两个维度是正交的,可以同时存在。

3.3 执行引擎的本质差异

MySQL 是逐行处理(row-at-a-time):查询执行器一次处理一行数据,走一遍算子链。

ClickHouse 是向量化执行(vectorized execution):一次处理一批数据(默认几千行为一个 Block),CPU 的 SIMD 指令可以同时对一批数值做运算,这也是它做聚合计算比 MySQL 快一到两个数量级的核心原因之一,而不仅仅是"列存"本身的功劳。


四、数据结构对比

4.1 MySQL InnoDB:B+树 + 聚簇索引

  • 数据按主键聚簇存储:一张表的数据物理上就是按主键顺序排列的一棵 B+树,叶子节点存完整行数据。
  • 二级索引的叶子节点存的是主键值,查询二级索引命中后还需要"回表"再查一次聚簇索引拿到完整行(除非是覆盖索引)。
  • 存储粒度是页(Page,默认 16KB),增删改查都是以页为单位操作,配合 Buffer Pool 做内存缓存。
  • 行式存储:一行的所有字段物理上挨在一起存储,适合"取一行的所有字段"这类操作。

4.2 ClickHouse MergeTree:列式存储 + 稀疏索引

这是和 MySQL 差异最大、也最需要重新理解的部分。

列式存储:每一列单独存成一个文件(更准确说是一组文件),而不是像 MySQL 那样一行数据挨在一起。好处是:

  • 查询只需要读取涉及的列,不相关的列完全不用碰磁盘,这对"宽表、只查几个字段做聚合"的场景是巨大的性能优势。
  • 同一列的数据类型相同、重复度高,压缩率远高于行式存储(LZ4、ZSTD 等)。

主键 = 稀疏索引,而不是唯一约束:这是 MySQL 用户最容易误解的一点。

  • MySQL 的主键:唯一、不可重复、精确索引到某一行。
  • ClickHouse 的主键(ORDER BY 定义的字段):允许重复值,索引里默认每隔 8192 行(index_granularity)才记录一个索引项,称为"稀疏索引"。它的作用不是精确定位某一行,而是快速定位"目标数据大概在哪个数据块范围内",然后在那个范围内做顺序扫描。
  • 换句话说,ClickHouse 的主键更像是给数据做了"物理排序 + 粗粒度目录",牺牲了精确定位的能力,换来了极低的索引存储开销和对范围扫描的极致优化。

数据以 Part 为单位组织

  • 每次 INSERT 会生成一个新的、不可变的数据目录,称为一个 Part(内部按列存储各字段数据、索引文件、校验文件等)。
  • Part 一旦写入就不再修改(这也是 UPDATE/DELETE 代价高的根本原因——不是改数据,而是要重写整个 Part)。
  • 后台会有 Merge 线程持续把多个小 Part 合并成大 Part,类似 LSM-Tree 存储引擎(如 RocksDB)的 compaction 机制,这也是 MergeTree 名字的由来。

4.3 数据结构对比小结

维度 MySQL InnoDB ClickHouse MergeTree
存储组织方式 行式,B+树 列式,按列独立文件
主键性质 唯一索引,精确到行 稀疏索引,粗粒度范围定位,允许重复
二级索引 B+树,需要回表 跳数索引(minmax/bloom_filter等),辅助跳过数据块
数据可变性 原地更新 数据不可变,靠新增 Part + 后台合并
压缩 页级压缩(可选) 列级压缩,压缩率高(同类型数据集中存放)

五、数据写入与查询的流动过程对比

5.1 MySQL 一次 INSERT/UPDATE 的内部流动

  1. 客户端发起 SQL,经过 Server 层解析、优化。
  2. InnoDB 引擎层:先写 Undo Log(用于事务回滚和 MVCC),修改内存中 Buffer Pool 里的数据页。
  3. Redo Log(prepare 阶段),保证崩溃恢复时数据不丢。
  4. Binlog(用于复制和恢复)。
  5. Redo Log 标记为 commit,事务提交完成。
  6. 脏页由后台线程异步刷回磁盘(不是每次提交都立刻落盘数据文件)。

整个过程围绕"保证单行/单事务的强一致性"设计,写入路径较长,但换来了严格的 ACID 保证。

5.2 ClickHouse 一次 INSERT 的内部流动

  1. 客户端发起 INSERT(通常是批量数据)。
  2. 数据按列拆分,直接写成一个新的 Part(写到磁盘的一个独立目录,包含各列数据文件 + 主键索引文件)。
  3. 写入即完成,不涉及复杂的日志协调(单条 INSERT 语句内保证原子性,但不同 INSERT 之间没有事务关联)。
  4. 后台 Merge 线程会周期性地把多个小 Part 合并成更大的 Part,减少查询时需要扫描的文件数量。
  5. 如果是分布式表写入,数据会先落到某个分片本地,再通过副本机制(基于 Keeper 协调)同步到其他副本。

可以看到,ClickHouse 的写入路径是"攒批 → 落盘成不可变文件 → 后台异步合并",天然适合高吞吐的批量写入,但不适合频繁的小批量、单行写入(会产生大量待合并的小 Part,拖累后台合并压力)。

5.3 一次 SELECT 查询的内部流动对比

MySQL(假设走索引):

SQL 解析 → 优化器选择索引 → 通过 B+树定位到具体的数据页
→ 读取整行数据(行式存储,一次拿到所有字段)→ 如需要则回表 → 返回结果

ClickHouse(假设做聚合查询):

SQL 解析 → 根据 WHERE 条件,用稀疏主键索引 + 跳数索引,
排除掉明显不涉及的数据块(Granule)
→ 只读取 SELECTWHERE 中涉及到的列(列裁剪,无关列完全不读)
→ 按 Block(几千行一批)做向量化计算
→ 多线程并行处理不同数据块,最后合并结果
→(如果是分布式表)各分片并行计算后,由发起查询的节点做最终汇总
→ 返回结果

一句话总结这个差异:MySQL 的查询流动是"精确定位 + 逐行处理",ClickHouse 的查询流动是"粗粒度过滤 + 批量并行扫描"。 这也解释了为什么用 ClickHouse 查一行数据(比如 WHERE id = 123)体验往往还不如 MySQL——它压根不是为这种场景设计的。


六、快速对照速查表

MySQL 里的概念/习惯 在 ClickHouse 里该怎么想
给查询慢的字段加个索引 先想清楚主要查询模式,设计好 ORDER BY;辅助字段考虑跳数索引或物化视图
主键唯一,用于精确查找一行 主键是排序键+稀疏索引,用于范围过滤,不保证唯一,也不适合查单行
高频 UPDATE 某个字段 尽量避免;改用"重新写入一批新数据"或设计成 Append-only 模型
用事务保证多语句的一致性 没有跨语句事务,一致性需要在业务层或通过幂等设计解决
主从复制做读写分离 副本(Replica)做高可用,分片(Shard)做横向扩展,两者正交
频繁地小批量 INSERT 尽量攒批(建议单批几千到几十万行),必要时前置消息队列或 Buffer 引擎
JOIN 大表随便写 小表放右边、控制右表大小(会被载入内存),大表 JOIN 大表要格外小心

七、多维度统计查询:要不要单独建表?

这是从 MySQL 转过来的用户很自然会问的问题,答案是:不一定,取决于数据量和查询模式,ClickHouse 的处理思路和 MySQL 不太一样。

7.1 数据量适中 / 探索性分析:不需要单独建表

直接对明细表做 GROUP BY 不同维度即可。列式存储 + 向量化执行让 ClickHouse 能在亿级行的明细表上直接做各种维度聚合,这本来就是它的设计初衷,不需要像 MySQL 那样依赖预先建好的索引或汇总表。

-- 同一张明细表,随便换维度统计
SELECT user_id, count() FROM events GROUP BY user_id;
SELECT toDate(created_at), sum(amount) FROM events GROUP BY toDate(created_at);
SELECT region, product_id, avg(amount) FROM events GROUP BY region, product_id;

这种"临时换维度、直接扫明细表"的能力,正是 ClickHouse 相对 MySQL 的核心优势之一。

7.2 数据量巨大 + 查询模式固定 + 要求低延迟:需要预聚合

如果每次都扫全量明细表会浪费资源(比如给实时看板用),需要预聚合。ClickHouse 提供三种主流做法,都不需要手动维护汇总表和 ETL:

(1)物化视图(Materialized View)—— 最常用

本质是"插入触发器":往明细表写入数据时,自动同步计算并写入一张预聚合的目标表(通常配合 AggregatingMergeTreeSummingMergeTree 引擎)。目标表确实是独立的表,但不需要手动写 ETL 刷新,ClickHouse 自动维护。

CREATE MATERIALIZED VIEW mv_daily_by_user
ENGINE = SummingMergeTree()
ORDER BY (user_id, day)
AS SELECT
    user_id,
    toDate(created_at) AS day,
    sum(amount) AS total_amount,
    count() AS cnt
FROM events
GROUP BY user_id, day;

如果有几种固定的高频查询维度(按用户、按天、按地区……),通常会建几个这样的物化视图,各自服务一种查询模式。

(2)Projection(投影)—— 不用建独立的表

可以理解成"给同一张表挂一套备用的物理组织方式"(换一个排序键,或预聚合),查询优化器会自动判断该用原表还是用某个 projection,查询 SQL 不用改。适合"同一张表偶尔要按另一个维度过滤/聚合"的情况,比物化视图更省心,但灵活性不如物化视图。

(3)Distributed + 分层汇总

数据量特别大、有多个分片时,可以做"明细表 → 分片本地预聚合 → 再全局汇总"的分层结构,减少跨节点传输的数据量。

7.3 对照 MySQL 理解

MySQL 里做类似的事,通常是手工建一张汇总表,靠定时任务(cron/ETL)或应用层触发器去刷新,本质是自己维护一份"预计算的缓存"。ClickHouse 的物化视图做的是同一件事,只是把"触发器 + 自动同步"做成了引擎自带的能力,不需要自己写同步逻辑。

7.4 建议的决策顺序

  1. 先直接查明细表,看性能够不够——大多数中小规模场景其实够用。
  2. 如果某几种查询是高频、固定维度、要求低延迟(比如给前端看板用),针对这几种模式建物化视图。
  3. 不要一上来就为每种可能的维度组合都建表,既浪费存储,维护成本也高,且违背了 ClickHouse "本来就能直接扫明细表做聚合"的设计初衷。

八、频繁更新的数据(如订单)能不能做实时同步?

MySQL 的表往往更新非常频繁(订单要经历下单、支付、发货、完成/取消等多次状态变化),而 ClickHouse 天生不擅长原地更新。这是不是意味着必须等数据"冷"下来才能同步进 ClickHouse?

答案是:不完全需要等冷,但要换一种建模思路——把"更新"变成"追加一条新记录",让 ClickHouse 的后台机制去处理去重。

8.1 方案一:ReplacingMergeTree(最常用)

每次订单状态变化,不做 UPDATE,而是直接 INSERT 一条新的快照行(同一个 order_idversion 更大):

CREATE TABLE orders_rt (
    order_id UInt64,
    status String,
    amount Decimal(10,2),
    updated_at DateTime64(3),
    version UInt64
) ENGINE = ReplacingMergeTree(version)
ORDER BY order_id;

后台 Merge 时,ClickHouse 会自动保留同一 order_idversion 最大的那一行,其余版本被合并清理。

关键坑:后台合并是异步的,查询时不保证旧版本已经清理完毕。有两种应对方式:

  • 查询语句加 FINAL(如 SELECT * FROM orders_rt FINAL),保证读到去重后的最新数据,但代价较高,不适合高并发实时查询。
  • 更推荐用 argMax 手动取最新值,性能好很多:
SELECT order_id,
       argMax(status, version) AS latest_status,
       argMax(amount, version) AS latest_amount
FROM orders_rt
GROUP BY order_id;

8.2 方案二:CollapsingMergeTree / VersionedCollapsingMergeTree

如果更关心的是实时聚合指标(比如"当前未完成订单总金额"),而不是订单本身的最新状态,这个引擎更合适。用 +1/-1 标记"生效"和"作废":

CREATE TABLE orders_agg (
    order_id UInt64,
    status String,
    amount Decimal(10,2),
    sign Int8  -- +1 表示当前有效,-1 表示撤销旧状态
) ENGINE = CollapsingMergeTree(sign)
ORDER BY order_id;

订单状态从 pending 变成 paid 时,写两行:一行 (旧status, 旧amount, -1) 撤销旧状态,一行 (paid, amount, +1) 表示新状态。后台合并时这一对会互相抵消,sum(amount * sign) 就是当前有效订单的实时汇总,不需要 FINAL,聚合查询效率很高。代价是写入端逻辑更复杂——需要知道"旧值是什么"才能写撤销行,通常要在 CDC/应用层维护上一次的状态快照。

8.3 更实际的选型:热冷分层,而不是全量塞进 ReplacingMergeTree

对订单这种"要经历多次状态流转、最终才稳定"的场景,业界更常见的做法是热冷分层,而不是把所有订单不分状态地全塞进 ReplacingMergeTree:

  • 热数据(进行中的订单):直接查 MySQL,或放进 Redis/应用内存做实时聚合。这部分订单量相对小(比如"最近 24 小时未完结的订单"),MySQL 本身完全扛得住。
  • 冷数据(终态订单:已完成/已取消/已退款):用 CDC 同步进 ClickHouse 做历史沉淀和大范围分析,因为终态订单不会再变,天然就是"追加写入",完全避开了 UPDATE 的麻烦。
  • 实时看板:把两边的结果在查询层拼起来——"今天的实时数据"查 MySQL/Redis,"过去 N 天的趋势"查 ClickHouse,前端做一次简单的合并展示。

推荐的整体架构如下:

MySQL 订单表(频繁 UPDATE)
        │
        ▼
   CDC 采集(Debezium / Canal)
        │
        ▼
   消息队列(可选,如 Kafka,用于削峰填谷)
        │
        ▼
ClickHouse 实时表(只追加写入,不做 UPDATE)
        │            ╲
        ▼             ╲── 后台 Merge(异步去重旧版本,不阻塞写入与查询)
      查询层
(用 argMax 取最新状态,或用 sum(amount*sign) 做实时汇总)

这本质上是经典的 Lambda 架构思路:用最擅长处理"少量、频繁变化数据"的系统去处理热数据,用最擅长处理"海量、稳定数据"的系统去处理冷数据,而不是强行让 ClickHouse 去做它不擅长的事情。

8.4 一句话总结

不是必须等数据"冷"了才能进 ClickHouse——用 ReplacingMergeTree/CollapsingMergeTree 配合 CDC,进行中的订单也能实时同步。但如果订单状态变化非常频繁(比如秒级更新),更稳妥的做法是让 ClickHouse 只接手"终态或准终态"的数据,进行中的高频变化部分交给 MySQL/Redis 处理,两边在查询层做拼接。


九、总结

  • ClickHouse 和 MySQL 不是替代关系,而是互补关系:MySQL 管好"事务型、精确到行"的数据,ClickHouse 管好"分析型、面向海量行聚合计算"的数据。
  • 从 MySQL 视角学习 ClickHouse,最重要的心态转变是:不要用"加索引优化单行查询"的思维去用它,而要用"设计好排序键、批量写入、列裁剪聚合"的思维
  • 架构上,MySQL 是"单机为主、复制为辅",ClickHouse 是"分片做扩展、副本做高可用",两者可以通过分片+副本的组合应对海量数据,这也是它能横向扩展到 PB 级数据分析的关键。

此博客中的热门博文

Elasticsearch 读写原理指南

### 1. 什么是 segment,里面装了什么? 在 Lucene(也是 Elasticsearch)里,索引被切分成若干 **segment(段)**,每个 segment 是一个完整的、只读的倒排索引单元。一个 segment 包含: * **倒排词典** —— 用 **FST(Finite‑State Transducer)** 以高度压缩的形式保存每个字段出现的所有 term 以及 term→ord 的映射。对应的磁盘文件是 `*.tim`(新版)或 `*.tis/*.tii`(旧版)。 * **倒排列表(postings)** —— 保存每个 term 出现的文档 ID、频次、位置信息等,文件名通常是 `*.doc`、`*.pos`、`*.pay`。 * **存储字段**(_source、store:true 的字段)—— 以二进制块的形式写入 `*.fdt` / `*.fdx`。 * **doc‑values、norms、向量** 等辅助结构,分别保存在 `*.dv`、`*.norm`、`*.tv` 等文件里。 * **deleted‑docs bitmap**(`*.del`),标记哪些文档已被删除或被更新。 所有这些文件在 segment **写入磁盘后即成为只读**,后续的查询只能读取,永远不会在原文件上进行增删改。 --- ### 2. 原始文档和 FST 为什么都在 segment 里? * **原始文档**:Elasticsearch 默认把完整的 JSON(_source)以及任何 `store:true` 的字段写入 segment 的 `*.fdt/*.fdx` 文件。每个 segment 保存自己的那部分文档,旧的 segment 在合并前仍然保留,直到合并后被删除。 * **FST**:每个字段的词典在每个 segment 中单独维护,采用 FST 进行前缀共享和字节压缩。这样即使同一个 term 在多个 segment 中出现,也会在每个 segment 里拥有独立的映射,查询时只需要在对应 segment 的 FST 中定位即可。 --- ### 3. 查询时到底是怎么遍历 segment 的? 1. **请求入口**      客户端的搜索请求先到达 **协调节点**,协调节点把请求 ...

LLM缓存详解

 可以把“大模型缓存”理解成: 把已经算过的结果(或中间结果)存下来,下次尽量复用 。但这里面其实分几层,不只是简单的“问题→答案”缓存。 1️⃣ 常见的几种缓存类型 (1)KV Cache(推理内部缓存) Transformer 在生成时,会把前面 token 的 Key/Value 向量 缓存下来。 本质:避免重复计算 attention 作用: 同一请求内部加速 特点: 👉 只对“同一上下文继续生成”有效 👉 不跨用户、不跨请求 这类缓存是你体感“流式输出越来越快”的原因之一。 (2)Prompt Cache(提示词缓存) 缓存的是: 相同(或高度相似)的 prompt → 对应的中间表示 / 输出 典型场景: 系统提示词(system prompt)很长 多轮对话里前文基本不变 👉 这里能省掉 前缀计算成本(prefill) (3)Embedding / 语义缓存(Semantic Cache) 这个才是你问题的关键 👇 不是按“字符串完全一致”,而是: 把问题转成向量 → 找“语义相似”的历史问题 → 直接复用答案 2️⃣ 为什么命中缓存成本低很多? 因为大模型推理成本主要在两块: (1)Prefill(吃 prompt) 复杂度 ~ O(n²) 很贵(尤其长 prompt) (2)Decode(逐 token 生成) 每个 token 都要算一遍模型 而缓存命中后: KV cache:不用重复 attention Prompt cache:不用重新 encode 语义缓存: 直接跳过模型推理 👉 相当于从: 几十~几百毫秒 + GPU算力 变成: 一次向量检索(毫秒级)+ 直接返回 所以成本差一个数量级是正常的。 3️⃣ “每个人问法不同,怎么命中缓存?” 这是核心难点,也是工程重点👇 ❌ 不能靠字符串匹配 比如: “今天天气怎么样” “今天外面热不热” 字符串完全不同 → 必须 miss ✅ 用语义相似度(Embedding) 流程一般是: 把问题转 embedding(向量) 在向量数据库里找 TopK 相似问题 如果相似度 > 阈值(比如 0.9) 直接返回缓存答案 一个简单示意 Q1: 北京天气怎么样 → embedding A Q2: 北京今天热吗 → embedding B cosine(A, B) ≈ 0.95...

事务的ACID是什么

 事务的 ACID 是数据库事务必须满足的四个基本性质,用来保证在并发和故障情况下数据的正确性与可靠性: A(Atomicity,原子性) 一个事务中的操作要么 全部成功 ,要么 全部失败回滚 ,不存在“只做了一半”的中间状态。 C(Consistency,一致性) 事务执行前后,数据库都必须处于 一致的合法状态 ,满足约束(如主键、外键、唯一性、业务规则等)。 I(Isolation,隔离性) 并发执行的多个事务之间 相互隔离 ,一个事务未提交的中间结果对其他事务不可见(具体强弱由隔离级别决定)。 D(Durability,持久性) 一旦事务提交成功,其结果会被 永久保存 ,即使系统崩溃也不会丢失(通常依赖 WAL/redo log 等机制)。 一句话记忆: 要么全做完、前后不破坏规则、互不干扰、做完不丢。