陈叔经营着鑫记茶餐厅,从一家小店开成了拥有 20 家分店的连锁品牌。过去,他想知道今天哪道菜卖得好,只能等打烊后,让各店长把订单拍照发给总部,会计连夜录入 Excel,第二天早上才能看到“昨日战报”。这份报表永远慢半拍:昨天冻柠茶爆单,等你知道时,今天原料已经订少了。这套“先记账、再搬运、后看报”的老流程,对应数据库世界的 OLTP 事务处理和 OLAP 分析处理。而如今数据库界正在流行一种新思路——HTAP,让一套系统同时干好“收银”和“看板”两件事,数据刚刚落账,报表立刻刷新,从此老板不用再等隔夜的账本。

岗位关联

掌握 HTAP 的架构设计逻辑,你就能在企业数据架构选型时,为实时交易与分析需求找到一体化方案,在职场上成为能打通“业务事务”与“数据分析”的关键人才。

目录
  1. 可视化演示
  2. 事务分析起源
  3. 行存事务特征
  4. 列存分析特征
  5. Raft日志同步
  6. 行列混存架构
  7. 一致性保障
  8. 架构权衡取舍
  9. 实时应用前景
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示行列混存双引擎、Raft日志实时同步与资源隔离查询路由的交互演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

行列混存双引擎 行存优化OLTP点查写入 列存优化OLAP扫描聚合 Row(TP) + Col(AP) = HTAP 总结:双引擎同时支撑事务与分析

事务分析起源

陈叔的茶餐厅,点单靠收银台,看报表靠总部“夜班会计”。每晚 11 点打烊,店长拍下所有手写单,会计忙到凌晨,把订单一张张录成电子表格。这种“白天做生意,晚上做账”的模式,在数据库世界有一个熟悉的名字:ETL。ETL 就像那个夜班会计,负责把业务系统(OLTP)一整天的流水搬运到分析系统(OLAP)。问题是,老板第二天看到的永远是“昨天”的数据——今天想调整菜单、想补货,已经来不及了。

数据库世界里,OLTP 是收银台:处理订单、扣库存、记余额,要求快、准、稳。OLAP 是总部的经营分析室:汇总几万家店的销量、利润、趋势,要求能扫描海量数据、做复杂聚合。两者负载完全不同,所以传统架构把它们拆成两套系统,中间用 ETL 搬运数据。这个搬运过程就是“隔夜报表”的来源:数据从产生到可见,至少要经过一个批处理周期。

HTAP(混合事务与分析处理)想做的事情,就是让一套系统同时具备“收银台”和“分析室”的能力。它不再需要每晚搬运数据,而是让业务写入之后,数据以近乎实时的方式同步到分析侧。这就像陈叔给店里装了一个“后厨大屏”,前台每接一单,大屏上的销量立刻跳动,不用再等会计熬夜。

所以,HTAP 的本质不是把 ETL 变快,而是尽可能让它消失。陈叔也不想要那个夜班会计了,他想要的是“刚下的单,大屏上立刻就能看到”。这正是混合事务分析架构要解决的核心诉求。

数据新鲜度延迟=T订单产生+T批处理搬运\text{数据新鲜度延迟} = T_{\text{订单产生}} + T_{\text{批处理搬运}}
这个公式说明:数据延迟来自“等订单攒够一批再搬运”的过程。只要批处理还存在,报表就永远是滞后的。
  1. 订单产生时刻每一笔交易发生的时间点,代表最新数据出现的时刻。
    T订单产生T_{\text{订单产生}}
  2. 批处理搬运耗时ETL 把增量数据从交易库搬到分析库所需的时间,可能横跨数小时。
    T批处理搬运T_{\text{批处理搬运}}
  3. 新鲜度延迟求和两者相加,就是从交易发生到分析端能看到该数据的总等待时长。
    数据新鲜度延迟=T订单产生+T批处理搬运\text{数据新鲜度延迟} = T_{\text{订单产生}} + T_{\text{批处理搬运}}

行存事务特征

收银台前,陈叔记流水账的习惯是一页一行:时间、菜品、数量、金额,一行就是一个完整的订单。想查“3 号桌点了什么”,翻到那一行就行,特别快。但如果想统计“今天冻柠茶卖了多少杯”,就得把整本流水账从头翻到尾,每一行都要看一遍,效率很低。这个流水账,就是数据库里的行存(行式存储):一行数据挨着放,主键索引能让人快速定位到某条记录。

行存结构把一条记录的所有字段存在一起,所以“按主键查单行”非常高效。这也正是 OLTP 事务的典型场景:下单、改库存、扣余额,每次只操作少量行,但要保证原子性、一致性、隔离性、持久性,也就是 ACID 事务。行存天然适合这种“一点一滴精准改”的负载,因为它把一条记录的上下文都放在一起,加锁、回滚都方便。

但行存的软肋在于聚合查询。假设订单表有 100 列,而你想统计每道菜卖了多少,数据库也得一行行读入 100 列的数据,哪怕你只关心“菜品名”和“数量”两列。读入的数据量等于行数乘以全行宽度,浪费巨大。

所以,行存是“翻单小能手”,却不是“算账好手”。陈叔的流水本适合前台点单,不适合后厨统计。这也是为什么数据库世界需要另一种存储形态,去承载分析型负载。

行存全表扫描I/O=N行×S行宽\text{行存全表扫描I/O} = N_{\text{行}} \times S_{\text{行宽}}
行越宽,扫全表越吃亏。收银台流水账用来“查一单”很快,用来“算总账”就慢了。
  1. 行数变量N 行表示订单表里有多少条记录,扫描时每一条都躲不过。
    N行N_{\text{行}}
  2. 行宽变量S 行宽表示一行数据包括所有字段的字节数,列越多越宽。
    S行宽S_{\text{行宽}}
  3. 乘积物理意义全表扫描时,磁盘需要把每一行的所有列都读进内存,再丢弃无关列。
    行存全表扫描I/O=N行×S行宽\text{行存全表扫描I/O} = N_{\text{行}} \times S_{\text{行宽}}

列存分析特征

后来陈叔在后厨装了一块大屏,大屏不记流水账,而是每一项菜品单独占一行,旁边列出销量、金额、门店。比如第一行写“冻柠茶 12 元 3200 杯”,第二行写“云吞面 22 元 1800 碗”。大屏想算总销售额,只需把“金额”这一列从上到下扫一遍,不需要看每个订单的原始明细。这种把相同字段归到一起的存储方式,就是列存(列式存储)。

列存最大的优势是“按需取列”。分析查询通常只关心少数几个列,比如菜品名、金额、时间,列存可以只读这些列,跳过其余几十个无关列。另外,同一列的数据类型相同,内容相似度高,压缩率非常可观。一个存着“朝阳店”“海淀店”的门店列,重复值一压缩,体积立刻缩水很多。读取量小,压缩比高,聚合自然就快。

但是,列存不是万能的。如果要在列存里修改一行记录,需要同时触及该行分布在各个列文件中的位置,更新成本极高;加上事务控制复杂,列存完全不适合高频小事务。后厨大屏擅长“看全局”,不擅长“改一笔”。

所以,行存管“每一笔”,列存管“每一面”。陈叔的店里,流水本和看板各有分工。HTAP 的野心,就是让两者在同一个屋檐下协作,而连接它们的枢纽,就是日志同步。

列存扫描I/O=N行×S目标列×(1−r压缩率)\text{列存扫描I/O} = N_{\text{行}} \times S_{\text{目标列}} \times (1 - r_{\text{压缩率}})
列存把“宽表扫全”变成“窄列扫局部”,再通过压缩进一步瘦身,所以分析又快又省。
  1. 目标列字节数S 目标列表示只读取查询需要的列,每行只贡献这一小段宽度。
    S目标列S_{\text{目标列}}
  2. 压缩率因子r 压缩率表示重复值被压缩后节省的空间比例,通常可以达到很高。
    1−r压缩率1 - r_{\text{压缩率}}
  3. 相乘得到扫描量行数乘以目标列宽度,再乘剩余比例,得到列存实际读盘量。
    列存扫描I/O=N行×S目标列×(1−r压缩率)\text{列存扫描I/O} = N_{\text{行}} \times S_{\text{目标列}} \times (1 - r_{\text{压缩率}})

Raft日志同步

鑫记茶餐厅有一个传菜口,服务员下单后把单子夹在转盘上,转盘一转,后厨大屏立刻显示“新订单:2 号桌冻柠茶两杯”。这个“转盘”就像分布式系统里的 Raft 日志:它把前台产生的新订单,按顺序广播给所有需要知道的地方。

Raft 是一种共识算法,用来让多台机器就“日志顺序”达成一致。在 HTAP 架构里,行存主库收到事务后,先把操作写进 Raft 日志,日志按顺序同步给列存副本;列存副本拿到日志,像后厨大屏刷新一样,把这些更改重放到自己的列文件上。这样一来,列存数据持续追踪行存的变化,分析查询随时都能看到最新的数据。

这就把传统 ETL 的“夜间批量搬运”转变成了“实时增量同步”。不需要再等打烊,不需要再攒批,每一笔事务提交后,日志同步在毫秒级完成。TiDB 的 TiFlash 就是通过 Raft 日志从 TiKV 行存同步数据的典型实现。同步窗口小,数据新鲜度从“隔天”变成了“现在”。

跑单铃一响,看板就动。陈叔终于不用等夜班会计把单据搬进 Excel 了,因为每一张新单都会通过“转盘”实时同步到后厨大屏。日志同步,就是 HTAP 的灵魂传送带。

T同步=T事务提交+T日志重放T_{\text{同步}} = T_{\text{事务提交}} + T_{\text{日志重放}}
同步耗时只由事务提交和日志重放两部分组成,没有批处理排队,因此能达到亚秒级延迟。
  1. 事务提交耗时行存主库完成一笔订单写入并落盘的时间。
    T事务提交T_{\text{事务提交}}
  2. 日志重放耗时列存副本读取 Raft 日志并更新自己列文件的时间。
    T日志重放T_{\text{日志重放}}
  3. 同步总耗时两端相加,就是新订单从写入到分析可见的整体窗口。
    T同步=T事务提交+T日志重放T_{\text{同步}} = T_{\text{事务提交}} + T_{\text{日志重放}}

行列混存架构

陈叔的店里现在有两样东西:收银台的流水本,还有后厨的大屏看板。流水本一行一行记录新订单,大屏把各项菜的销量列成表。两者各司其职,中间靠传菜口转盘联动。这就是 HTAP 的行列混存架构:一套数据库,同时部署行存引擎和列存引擎。

以 TiDB 为例,TiKV 是行存引擎,掌管事务写入和主键点查;TiFlash 是列存引擎,专门服务分析查询。两个引擎并不孤立:TiKV 中产生的数据变更,通过 Raft 日志实时同步到 TiFlash,让列存副本持续保持最新。用户发出 SQL 时,优化器会自动判断:点查走 TiKV,聚合分析走 TiFlash,甚至同一张表的不同查询也能分流到不同引擎。CockroachDB 同样用 Raft 日志驱动副本同步,在分布式 SQL 层提供类似 HTAP 能力。

这就像一个数据库里有两个部门:业务部负责接单,分析部负责出报表,而 Raft 日志是两部门之间的“对讲机”。对用户来说,他们只面对同一个数据库入口,不需要关心数据被拆到了哪个引擎。

既有流水本,又有看板,还有联动机制,这就是“一库双引擎”的行列混存架构。对陈叔来说,他不需要分别经营两套系统,只需要一个前台、一个后厨,然后让传菜口把两者连起来。

读取总成本=min⁡(C行存点查,  C列存聚合)\text{读取总成本} = \min(C_{\text{行存点查}},\; C_{\text{列存聚合}})
HTAP 的优化器会像聪明的店长一样,自动选择“最划算”的查询路径:单子去前台,统计去大屏。
  1. 行存点查成本按主键快速定位一条记录所需的 I/O 和计算成本。
    C行存点查C_{\text{行存点查}}
  2. 列存聚合成本按目标列扫描并聚合所需的 I/O 和计算成本。
    C列存聚合C_{\text{列存聚合}}
  3. 取最小值系统自动选择成本更低的路径,实现“一个入口,两种加速”。
    读取总成本=min⁡(C行存点查,  C列存聚合)\text{读取总成本} = \min(C_{\text{行存点查}},\; C_{\text{列存聚合}})

一致性保障

陈叔有一个习惯:每天下午三点,他要在手机上看看各分店实时销售。他特别怕看到一半,数字突然跳来跳去——比如刚看到“冻柠茶 1000 杯”,下一秒变成 998 杯,再下一秒又变成 1002 杯。他希望每一眼看到的都是“三点整那一刻”的稳定快照。HTAP 里的快照隔离,就是为这种需求设计的。

在分布式系统中,数据分散在多个节点,行存和列存也在不断同步,如何保证一个查询看到“一致的瞬间”?答案是全局时间戳。每个查询开始时,向中心时钟服务申请一个时间戳 TSO,这个查询只读取提交时间早于该时间戳的数据版本。无论数据同步到哪一步,查询永远基于同一个时间点构建快照,不会看到正在写入的中间状态。

配合分布式事务协议,行存写入和日志同步都带有明确的时间顺序,列存副本重放日志后,就能提供与行存一致的历史快照。这种机制让“读分析副本”和“读交易主库”得到的数据逻辑一致,只是时效上有一个毫秒级的同步窗口。

就像陈叔的三点快照:一到三点,账房“咔嚓”一声把所有账页拍下来,之后即使前台又接了几十单,也不影响他分析三点那一刻的经营状况。一致性保障,让“实时看板”既新又稳。

Read(TSOq)={tx∣vtx≤TSOq}\text{Read}(TSO_q) = \{ tx \mid v_{tx} \le TSO_q \}
给查询一个“拍照时刻”,它看到的就是那一瞬间所有已提交数据的集合,不会被后来的新订单打扰。
  1. 查询快照时间戳TSO q 是查询开始时获得的全局时间戳,相当于快门按下的一刻。
    TSOqTSO_q
  2. 事务提交版本号v tx 是每个事务获得单调递增版本号,用来标记数据的提交顺序。
    vtxv_{tx}
  3. 可见版本条件只有提交版本号不大于查询时间戳的事务才可见,于是构成稳定快照。
    Read(TSOq)={tx∣vtx≤TSOq}\text{Read}(TSO_q) = \{ tx \mid v_{tx} \le TSO_q \}

架构权衡取舍

双本账很好用,但陈叔在推行的过程中发现了问题:首先,多一套大屏看板意味着多一份设备成本;其次,传菜口转盘虽然快,但传送带也会偶尔卡顿,需要保养;最后,如果要确保前后台任何时候看到的数字都完全一致,就得给每道菜装上更复杂的“确认按钮”,让出菜速度变慢一些。

对应到 HTAP 架构,这些就是设计取舍:行存和列存同时存储一份数据的逻辑内容,磁盘成本必然上涨;日志同步虽然快,但网络和副本状态仍可能带来毫秒级延迟;强一致性要求更高,分布式事务和共识协议的开销也更大。一个 HTAP 系统不可能在所有维度都做到最优。

因此,架构师需要按业务场景选择平衡点。核心交易系统对一致性要求极高,但分析负载不大;实时风控系统希望数据尽量新,但偶尔读到延迟几百毫秒也可接受;轻量经营报表追求全链路简单,不需要复杂的数仓建模。理解了权衡,才能明白为什么很多系统默认用异步复制或弱一致性来换性能。

最后陈叔只给爆款菜和重点分店装了大屏,普通小店的经营数据仍然用每日报表。这就是基于场景的取舍:全面的 HTAP 是理想,落地时永远要算清成本账。

总体成本=C存储+C同步+C一致性\text{总体成本} = C_{\text{存储}} + C_{\text{同步}} + C_{\text{一致性}}
存储、同步、一致性三大开销此消彼长。系统设计不是追求每一项满分,而是找到适合业务的最优组合。
  1. 存储副本成本行列两套存储占用额外磁盘空间。
    C存储C_{\text{存储}}
  2. 同步链路成本日志广播、网络传输、副本重放消耗计算资源。
    C同步C_{\text{同步}}
  3. 一致性协议成本保证强一致需要更复杂的协议和更多等待。
    C一致性C_{\text{一致性}}

实时应用前景

装好双本账之后,陈叔的办公方式彻底变了。以前他每周开一次经营会,看一堆 Excel。现在他可以端着一杯冻柠茶,盯着手机上的实时看板:早高峰哪个分店爆单,哪道菜在午市突然走量,哪个门店库存快见底,都一目了然。他还能在直播带货的瞬间,看到某个门店的实时销量冲高,立刻调度中央厨房加货。

这样的实时能力,在行业里正被广泛需要。实时风控是典型场景:支付系统一边处理交易事务,一边用列存分析近期交易模式,在毫秒级识别异常,而不是等事后跑批才拦截。实时经营分析让连锁零售、电商运营团队按分钟观测渠道效果,快速调整策略。HTAP 还大幅简化了数据链路:不再需要维护复杂的 ETL 任务和两套数据库,一套系统完成读写与分析,运维成本随之下降。

这套架构之所以能带来这些价值,是因为它将“事务”、“同步”、“分析”三段链路压缩到了一套系统内。业务请求从写入到分析可见,经过的每一步都可以被度量和优化。

陈叔的茶餐厅从一个靠“隔夜账”经营的连锁店,进化成了“实时看板”驱动的敏捷组织。HTAP 的未来,正是让更多企业从“事后诸葛亮”走向“现在就能拍板”。

端到端延迟=T事务+T日志同步+T分析查询\text{端到端延迟} = T_{\text{事务}} + T_{\text{日志同步}} + T_{\text{分析查询}}
事务一提交,日志立即同步,分析立刻查询——三步串联,把决策从“T+1 天”压缩到“秒级”。
  1. 事务处理时间业务请求在行存引擎上完成事务写入的时间。
    T事务T_{\text{事务}}
  2. 日志同步时间Raft 日志把变更传播到列存副本的时间。
    T日志同步T_{\text{日志同步}}
  3. 分析查询时间列存引擎执行聚合查询返回结果的时间。
    T分析查询T_{\text{分析查询}}
  4. 端到端含义三者之和 = 业务发生到决策依据就绪的总时长。
    端到端延迟=T事务+T日志同步+T分析查询\text{端到端延迟} = T_{\text{事务}} + T_{\text{日志同步}} + T_{\text{分析查询}}

AI 应用场景

实时交易风控

阶段:数据准备与特征计算

解决的问题:传统风控依赖事后批处理,欺诈交易可能已经完成才被识别;HTAP 让交易事务和分析查询在同一系统内协作,风控特征实时更新,模型推理前就能拿到最新的行为序列。

具体案例:支付平台处理一笔扫码支付时,行存记录交易,列存同步该用户过去 5 分钟的交易频率与金额聚合。风控模型在交易进行中查询列存特征,识别“短时间高频小额试探”模式,直接触发拦截,毫秒级完成决策。

连锁餐饮实时经营分析

阶段:模型部署后的业务反馈分析

解决的问题:连锁餐饮总部希望按分钟跟踪各门店的菜品销量和库存,传统做法需要频繁跑批,数据链路冗长;HTAP 让“订单写入即分析”,运营大屏实时刷新。

具体案例:某连锁茶饮品牌上线 HTAP 后,总部可从集团大屏同时看到全国门店的实时销量排名、单品趋势和库存水位。当某区域门店冻柠茶销量激增时,系统自动标识补货建议,运营团队立即安排调拨。

实时数据服务链路简化

阶段:数据管线建设

解决的问题:传统架构需要维护交易库、ETL 任务、数仓分层等多个环节,运维复杂且延迟叠加;HTAP 用一套系统承载 OLTP 和 OLAP,简化端到端数据链路。

具体案例:一家电商公司把订单服务和分析报表从两套系统迁移到一套 HTAP 数据库后,取消了每天凌晨的 ETL 批处理任务。数据分析师可以随时用 SQL 直接查询实时订单数据,不再依赖固定时间刷新的数仓表。

Python 演示

python82 行
"""HTAP 最小模拟:一套账本同时支撑事务与实时分析。"""import timefrom collections import defaultdict  class OrderRowStore:    """行存储账本:一笔订单追加一行,适合点查。"""     def __init__(self):        self.order_records = []        self.log_entries = []     def add_order(self, branch, dish, amount):        record = {            "id": len(self.order_records) + 1,            "branch": branch,            "dish": dish,            "amount": amount,            "ts": time.time(),        }        self.order_records.append(record)        self.log_entries.append(("INSERT", record))     def query_order(self, order_id):        for item in self.order_records:            if item["id"] == order_id:                return item        return None  class SaleColumnStore:    """列存储看板:按列聚拢数据,承接分析查询。"""     def __init__(self):        self.branch_map = defaultdict(list)        self.dish_map = defaultdict(list)        self.amount_list = []        self.log_offset = 0     def replay(self, row_store):        """模拟 Raft 日志同步:把新日志重放到列存。"""        while self.log_offset < len(row_store.log_entries):            _, item = row_store.log_entries[self.log_offset]            self.branch_map[item["branch"]].append(item["id"])            self.dish_map[item["dish"]].append(item["id"])            self.amount_list.append(item["amount"])            self.log_offset += 1     def aggregate_by_dish(self):        total_map = {}        for dish, ids in self.dish_map.items():            total_map[dish] = sum(self.amount_list[i - 1] for i in ids)        return total_map  def main():    row_store = OrderRowStore()    col_store = SaleColumnStore()    orders = [        ("朝阳店", "冻柠茶", 12),        ("朝阳店", "云吞面", 22),        ("海淀店", "冻柠茶", 12),        ("朝阳店", "冻柠茶", 12),        ("海淀店", "牛腩饭", 35),    ]    for branch, dish, amount in orders:        row_store.add_order(branch, dish, amount)     time.sleep(0.1)    print("传统 ETL 方式:等批处理 0.1 秒后,报表才能看到新订单")     col_store.replay(row_store)    print("HTAP 方式:日志同步完成,实时聚合结果如下")    for dish, total in col_store.aggregate_by_dish().items():        print("  {} 总销售额:{} 元".format(dish, total))     order = row_store.query_order(1)    print("行存点查订单 1:{}".format(order["dish"]))  if __name__ == "__main__":    main()
目的:用两个类模拟行存与列存,用 replay 模拟 Raft 日志同步,直观展示“一边写事务、一边同步分析”的 HTAP 工作方式。
  1. self.log_entries.append(("INSERT", record))每笔新订单都要追加一条日志,作为后续同步的“传菜单”。
  2. def replay(self, row_store)把行存中尚未同步的日志逐条重放到列存,对应 HTAP 的实时同步环节。
  3. self.log_offset += 1记录同步进度,避免重复读取旧日志。
  4. total_map[dish] = sum(self.amount_list[i - 1] for i in ids)列存只聚合“金额列”,对应压缩后高效分析。
  5. row_store.query_order(1)行存仍然保留主键点查优势,一套系统里两种能力并存。

常见误区

误区很多初学者看到 TiFlash、列存副本,第一反应是“这不就是给原来的表加个列存索引吗?跟 MySQL 的二级索引差不多。”

正解HTAP 远不止索引。列存索引只在单机存储引擎内部加速查询;而 HTAP 需要解决分布式事务、Raft 日志复制、全局时间戳、行列副本一致性等一系列问题。它是一整套“事务与分析协同”的架构,不是局部优化。好比给收银台加一块计算器不叫“经营分析系统”,真正的系统要打通前台的每一笔单和后台的实时看板。

为何常见:因为单机数据库已经有“行转列加速”的成熟做法,容易把分布式 HTAP 和单机索引优化混为一谈。

误区HTAP 宣传“实时分析”,于是有人以为数据仓库可以淘汰,所有分析都用 HTAP 就行。

正解HTAP 擅长轻量、实时的聚合查询,但复杂的数据建模、海量历史数据归档、机器学习特征工程,仍然需要数据仓库和湖仓一体平台。后厨大屏能看到今日实时销量,但公司总部的年度经营报告还是要靠完整的财务系统。HTAP 和数据仓库是互补关系,不是替代关系。

为何常见:厂商宣传强调“实时”和“一体化”,容易让用户忽略复杂分析场景和成本边界。

误区很多人认为 Raft 日志同步是瞬时的,行存写一条,列存立刻就有,两边数据 100% 同时一致。

正解日志同步需要网络传输、副本落地和重放时间,因此存在一个毫秒级的同步窗口。HTAP 通常提供快照隔离:查询看到的是某个时间点的一致版本,而不是每一纳秒都与行存完全同步。跑单铃响了,后厨大屏刷新也需要一瞬间,这个瞬间就是同步延迟。

为何常见:“日志同步”听起来是即时的,人们容易忽略物理传输和一致性协议的实际开销。

误区既然列存聚合快、压缩率高,为什么不把所有表都换成列存?这样事务和分析一起变快。

正解列存对单行点查和高频更新并不友好:更新一行需要改动多个列文件,事务的加锁与回滚也更复杂。收银台记一笔账要快速定位并修改当行,这是行存的强项;后厨看板做全局统计,才是列存的舞台。用错场景,列存反而会拖慢系统。

为何常见:人们容易把“分析快”误解为“所有操作都更快”,忽略了不同存储结构的设计初衷。

面试题

Q1. HTAP 的核心设计目标是?

单选题 · easy

  • A.一套系统兼顾事务与实时分析← 正确答案
  • B.用离线批处理替代实时查询
  • C.把数据库迁移到云上部署
  • D.只优化单行点查的响应速度

答案:A。HTAP 是混合事务与分析处理,核心是用一套系统同时支撑 OLTP 和 OLAP。B 是传统 ETL 思路,C 与 HTAP 无直接关系,D 只描述行存点查场景,都不是 HTAP 的目标。

Q2. 关于行存储结构,下列哪项说法正确?

单选题 · easy

  • A.按主键定位单行记录高效← 正确答案
  • B.全表聚合扫描最有效率
  • C.统计时可跳过全部无关列
  • D.天然拥有极高的压缩率

答案:A。行存把一条记录的字段放一起,适合按主键点查。全表聚合需要扫描整个行宽,效率低,B 错误;跳过无关列是列存特性,C 错误;同列重复值集中才是高压缩的前提,行存不具备这种优势,D 错误。

Q3. 列存储在分析场景下更高效,主要原因是?

单选题 · easy

  • A.只读目标列且压缩比高← 正确答案
  • B.按行整行读取再做过滤
  • C.每次更新只需改单个文件
  • D.完全不需要主键与索引

答案:A。列存按列聚拢数据,聚合查询只需扫描目标列,且同列相似度高利于压缩。B 是行存行为,C 是列存的劣势(更新要动多个列文件),D 过于绝对,列存也可以有主键。

Q4. 在 HTAP 同步机制中,Raft 日志的核心作用是什么?

单选题 · mid

  • A.按序复制行存更新到列存← 正确答案
  • B.负责 SQL 解析与执行计划
  • C.把分析结果合并回行存
  • D.定期清理过期的事务日志

答案:A。Raft 日志保证多副本按相同顺序应用写入,列存副本重放日志就完成了数据同步。SQL 解析由优化器处理,B 错误;分析结果不回写行存,C 错误;清理日志是存储管理细节,不是核心作用。

Q5. 传统 ETL 批处理带来的主要问题是?

单选题 · mid

  • A.分析数据存在明显新鲜度延迟← 正确答案
  • B.事务处理吞吐量大幅提升
  • C.行列存储成本显著下降
  • D.分析查询并发数自动翻倍

答案:A。ETL 需要攒批搬运,报表常常是 T+1 的隔夜数据,这正是新鲜度延迟的根源。B 是系统升级的效果,与 ETL 无关;C 和 D 的说法不符合实际。

Q6. 以下哪些属于 HTAP 架构的关键特点?

多选题 · mid

  • A.单系统内同时支撑 OLTP 与 OLAP← 正确答案
  • B.借助 Raft 日志做实时复制同步← 正确答案
  • C.采用行列混存服务不同负载← 正确答案
  • D.必须放弃分布式事务能力

答案:ABC。HTAP 的核心即单系统混合两种负载,A 正确;Raft 日志保证行存到列存的高效同步,B 正确;行列混存是典型实现手段,C 正确。HTAP 不但不放弃事务能力,反而要维护分布式事务,D 错误。

Q7. HTAP 引入列存副本后,存储成本会怎样变化?

单选题 · mid

  • A.增加额外的磁盘副本开销← 正确答案
  • B.成倍降低全部磁盘开销
  • C.与纯行存完全保持一致
  • D.完全不需要额外存储空间

答案:A。行列混存意味着同一份逻辑数据在行存和列存各存一遍,虽然列存压缩可以节省一些空间,但总体仍会增加磁盘开销。B、C 与事实不符,D 明显错误。

Q8. HTAP 保证读写一致的常见手段包括哪些?

多选题 · hard

  • A.全局时间戳提供快照隔离← 正确答案
  • B.分布式事务协调多节点写入← 正确答案
  • C.日志按序重放到分析副本← 正确答案
  • D.拒绝所有并发事务写入

答案:ABC。全局时间戳让查询看到一致快照,A 正确;分布式事务让多节点提交保持一致状态,B 正确;日志按序重放保证列存副本与行存逻辑一致,C 正确。拒绝并发写入等于放弃事务能力,D 错误。

Q9. 为什么 HTAP 的分析查询需要全局时间戳快照?

单选题 · hard

  • A.为查询提供统一的一致性视角← 正确答案
  • B.为了压缩列存数据节省空间
  • C.为了减少 SQL 的解析次数
  • D.为了降低日志同步的网络开销

答案:A。查询请求全局时间戳,就能以某个确定时刻的版本集合作快照,避免看到行存和列存不同步的中间状态。这与压缩、SQL 解析、网络开销没有直接关系。

Q10. 关于 TiDB 的 HTAP 架构,以下说法正确的有?

多选题 · extreme

  • A.TiKV 负责行存事务处理← 正确答案
  • B.TiFlash 提供列存分析能力← 正确答案
  • C.TiFlash 通过 Raft 日志同步← 正确答案
  • D.TiFlash 依赖人工定时导入

答案:ABC。TiKV 是行存引擎,支撑事务写入,A 正确;TiFlash 是列存引擎,负责分析查询,B 正确;TiFlash 通过 Raft 日志从 TiKV 实时同步数据,C 正确。D 错,TiFlash 的同步是自动的,不需要人工导入。