目录
简要流程
详细流程
1. UPDATE 语句执行流程
2. 如何更新表的数据
3. 是否支持事务
总结关键点
简要流程
- 前端处理(FE):
-
- 解析 SQL 并验证主键条件
- 生成包含主键列表和新值的更新计划
- 按主键哈希分发到对应 BE
- 后端执行(BE核心流程):
-
- 通过主键索引快速定位数据位置
- 采用"读-改-写"模式(非就地更新)
- 对同一主键的多版本数据保留最新值
- 提交与清理:
-
- 元数据原子切换确保查询一致性
- 异步 Compaction 回收旧数据空间
- 写时复制(Copy-on-Write)机制保证读稳定性
详细流程
StarRocks 的 UPDATE
语句是其 异步主键更新模型 的核心功能(自 2.3 版本引入,并持续优化),专为高效处理按主键进行批量更新的场景设计。其实现与传统的 OLTP 数据库有显著区别,充分利用了列式存储和 MPP 架构的优势。下面详细介绍你关心的三个方面:
1. UPDATE 语句执行流程
StarRocks 的 UPDATE
操作是一个异步、批处理的过程,主要发生在后台的 BE(Backend)节点上,大致流程如下:
- 用户提交 UPDATE 语句:
-
- 用户通过 MySQL 客户端或其他兼容工具向 FE(Frontend)提交标准的 SQL
UPDATE
语句。 - 示例:
UPDATE table_name SET column1 = value1 [, column2 = value2 ...] WHERE pk_column = some_value [AND ...];
- 用户通过 MySQL 客户端或其他兼容工具向 FE(Frontend)提交标准的 SQL
- FE 解析与计划生成:
-
- FE 解析 SQL,验证语法、权限、目标表和列是否存在、WHERE 条件是否包含完整主键(或其等价条件)。
- FE 生成一个逻辑更新计划。这个计划主要包含两部分信息:
-
-
- 需要更新的行定位信息: 基于 WHERE 条件(必须能精确定位到主键)计算出哪些主键值对应的行需要被修改。
- 新的列值: 指定的 SET 子句中的新值。
-
- 数据分发到 BE:
-
- FE 将逻辑更新计划(主要是主键列表和对应的新值集合)分发给存储了相关数据分片的 BE 节点。
- 分发策略基于主键的哈希值,确保包含特定主键行的 Tablet(数据分片)所在的 BE 收到该行的更新请求。
- BE 执行更新操作(核心 - 读改写):
-
- 每个 BE 收到属于自己 Tablet 的更新任务后,执行以下关键步骤:
-
-
- a. 定位数据文件: 利用主键索引快速定位包含目标主键行的数据文件(Segment 文件)。主键索引是存储在内存中的。
- b. 读取原始行: 读取包含目标行的整个 Segment 文件(或相关的行组)。
- c. 应用更新: 在内存中,根据 UPDATE 语句的 SET 子句,修改读取到的目标行的对应列值。
- d. 排序与去重: 对这批更新后的行(可能包含同一主键的多次更新)按主键排序,并只保留最新版本(基于操作序列号或时间戳)。这是保证主键唯一性和最终一致性的关键。
- e. 写入新文件: 将排序去重后的这批更新行(连同该 Segment 中未修改的行)写入一个新的 Segment 文件。原始文件不会被就地修改。
- f. 提交元数据: 向 FE 报告新生成的 Segment 文件信息,并更新元数据(如版本号)。
-
- 垃圾回收(Compaction):
-
- 新 Segment 文件写入成功后,旧的 Segment 文件(包含被更新行原始数据)会被标记为可删除。
- 后台的 Compaction 进程(Base Compaction 或 Cumulative Compaction)会异步地将包含多个版本数据的 Segment 文件合并压缩,物理删除被覆盖的旧数据行,回收存储空间。
2. 如何更新表的数据
- 基于主键: UPDATE 必须指定完整的主键或能等价推导出主键的 WHERE 条件(例如
WHERE primary_key_col = ?
)。这是 StarRocks 高效定位数据的基础。 - 列式更新: 在 SET 子句中指定需要更新的列及其新值。未指定的列保持不变。
- “读-改-写”模式: 核心机制是读取包含目标行的原始数据块 -> 在内存中修改目标列 -> 将修改后的整行(连同该块中未修改的行)写入新文件。不是直接在原存储位置修改位图或单个值。
- 批量处理: 一次 UPDATE 操作通常涉及一批行的更新(即使 SQL 看起来只更新一行,内部也可能批量处理)。BE 在内存中处理一批更新,排序去重后一次性写入新文件,效率远高于单行更新。
- 写时复制 (Copy-on-Write): 更新操作通过创建包含新数据的新文件(Segment)来实现,原始文件保持不变直到被异步回收。这保证了高并发读操作的稳定性(读操作总是访问旧的、一致的文件版本,直到新版本提交)。
- 原子性与版本化: 新 Segment 文件的写入和元数据的更新(版本切换)是原子的。查询在某个时间点看到的总是某个一致的数据版本。
3. 是否支持事务
StarRocks 的 UPDATE
语句在单个语句级别提供原子性和持久性:
- 原子性 (Atomicity per Statement):
-
- 单行更新: 对一个主键行的所有列更新是原子的。要么所有指定列都更新成功,要么都不更新。
- 多行更新: 同一批处理内更新的多行操作也具有原子性。这意味着在 BE 处理一批更新时,这批中的所有行更新要么全部成功写入新 Segment 并提交(元数据更新),要么全部失败(例如 BE 崩溃)。用户不会看到部分更新的状态。
- 隔离性 (Isolation):
-
- StarRocks 使用 MVCC (多版本并发控制)。UPDATE 操作创建数据的新版本。
- 正在进行的 UPDATE 操作不会阻塞读操作。读操作(如 SELECT)会读取操作开始时已提交的最新版本数据(快照隔离),看不到正在进行的 UPDATE 产生的中间状态或未提交的新版本。
- 多个并发的 UPDATE 操作修改同一主键行时,基于操作序列号或时间戳,只有最后一个成功的 UPDATE 会生效(最终一致性)。在 BE 处理阶段,排序去重步骤保证了这一点。用户可能会看到基于主键的“最后写入获胜”行为。
- 持久性 (Durability): 一旦 UPDATE 操作成功提交(元数据更新完成),数据就持久化写入磁盘。即使发生节点故障,已提交的数据也不会丢失。
- 多语句事务:
-
- 社区版: 不支持 跨多个 SQL 语句(如
BEGIN; UPDATE ...; UPDATE ...; COMMIT;
)的 ACID 事务。每个UPDATE
语句是独立提交的。 - 企业版: 支持 有限的多语句事务 (自 3.0 版本引入)。在一个显式的
BEGIN
/COMMIT
/ROLLBACK
块内执行的多个 DML 语句(INSERT, UPDATE, DELETE)可以作为一个原子操作提交或回滚。这是通过 FE 协调和内存队列实现的,但有容量和超时限制,主要用于小批量、短时操作。它不是传统 OLTP 数据库那种支持长时间运行、大事务的强事务模型。
- 社区版: 不支持 跨多个 SQL 语句(如
总结关键点
- 流程: 解析 -> 定位主键 -> 分发 -> (BE)读原始数据 -> 改内存数据 -> 排序去重 -> 写新文件 -> 提交元数据 -> 异步回收旧文件。
- 更新机制: 基于主键,批量处理,读-改-写模式,写时复制(创建新文件),利用主键索引和排序去重保证效率与主键唯一性。
- 事务: 单条 UPDATE 语句具有原子性和持久性,通过 MVCC 提供快照隔离级别的读一致性。社区版不支持多语句事务,企业版提供有限的多语句事务支持。
理解 StarRocks 的 UPDATE 是面向分析场景优化的、基于主键的异步批量更新机制,而非 OLTP 式的逐行实时更新,对于正确使用和性能调优至关重要。它非常适合数据修正、缓慢变化维度(SCD Type 1/2)、标签更新等场景。