想象一家大型自助餐厅的后厨:角落里有一个巨大的冷藏库,所有食材不论贵贱都能囤放,这是“数据湖”;紧挨着灶台的备菜台,只摆放洗净切好、贴好标签的半成品,这是“数据仓库”。所谓“湖仓一体”,就是给冷藏库装上温控系统、玻璃门和标签管理,让备菜台与冷库真正打通。而 Trino、Spark SQL、Photon 三位“掌勺厨师”,各自用不同的策略为你把数据做成一道道菜——有的跑得快擅长小炒,有的耐得住性子炖大菜,有的握着高科技锅铲又快又好。这篇文章就带你走进这间后厨,看清三位厨师的真功夫,学会按需点单。
理解三大湖仓查询引擎的架构差异与性能边界,能让你在真实业务中做出更省钱的选型决策,也能在与数据平台团队协作时说到点子上。
可视化演示
动态展示Trino流水线/Spark DAG/Photon向量化三种执行模型下SQL查询从coordinator经多stage到结果的数据流路径,切换引擎观察延迟与吞吐差异。
本节配有交互动效(进入视口后自动加载,约 3KB)。
湖仓一体基础
想象一家大型自助餐厅的后厨:角落里的巨型冷藏库可以囤放任何食材,不管是整扇猪肉还是没拆箱的冻虾,这是“数据湖”——存储便宜、格式随意,但想直接用它做菜就费劲了;而灶台边整齐的备菜台只放洗净切好、贴着标签的半成品,这是“数据仓库”——做菜快,但把每样食材都精细加工的成本很高。所谓“湖仓一体”(Lakehouse),就是给冷藏库装上玻璃门、温控和标签系统,让备菜台和冷库真正打通:既享受冷库的低成本,又拥有备菜台的秩序。
在真实的大数据世界,数据湖通常指基于 S3、OSS 等廉价对象存储,存放 Parquet、ORC 等原始文件,灵活但缺少约束;数据仓库则用严格 Schema 和治理流程保证数据质量,查询高效但建设周期长、成本高。湖仓一体架构在这两者之间取了平衡:在廉价存储之上叠加事务(ACID)、Schema 强制、索引与元数据管理,典型技术包括 Delta Lake、Iceberg、Hudi 等开放表格式。而无论哪种架构,真正把数据“端上桌”的是查询引擎——它们的角色就像后厨里的厨师,负责把食材变成菜品。
理解湖仓的存储与计算分离模式,可以从总体成本模型入手:
湖仓一体的精髓,就是像给冷库装上直达备菜台的传送带一样,让数据不必反复装卸。理解了这一点,后面三位厨师的功夫高低,就有了评判的场地。
- 总成本\( C_{\text{total}} \) 是湖仓架构的整体拥有成本,决定技术选型的预算边界。
- 存储成本\( C_{\text{storage}} \) 是数据存放的费用,数据湖用廉价对象存储把这个数字压得很低。
- 计算成本\( C_{\text{compute}} \) 是查询引擎执行 SQL 所消耗的 CPU 与内存费用。
- 网络成本\( C_{\text{network}} \) 是数据跨系统搬移的开销,湖仓一体通过计算下推减少“搬运”。
引擎架构对比
后厨里有三位风格迥异的厨师长:跑单小哥(Trino)接到订单就冲向冷库,脚步飞快但怀里抱不了太多食材;中央厨房团队(Spark SQL)喜欢先让帮工把成吨食材分批处理成半成品,再统一开火;特级厨师(Photon)握着高科技厨具,做起菜又快又好。三位在处理同一份订单时,策略、成本与瓶颈完全不同。
这种差异来自底层架构。Trino 采用 MPP(大规模并行处理)无状态架构:一个 Coordinator 负责解析 SQL、生成执行计划并调度任务,多个 Worker 并行执行、互不共享状态,就像一群跑单小哥各自独立取货。Spark SQL 采用 Driver + Executor 的弹性分布式架构:Driver 编排 DAG(有向无环图,把计算拆成有依赖关系的阶段),Executor 执行具体任务,阶段之间通过 Shuffle 交换中间数据,就像中央厨房里各操作台之间传递半成品。Photon 则是 Databricks 用 C++ 重写的 Spark 执行引擎,不改变 Spark 的调度模型,只把最底层的运算算子换成向量化版本,相当于给中央厨房换了一套高速厨具。
并行系统有一个经典定律——Amdahl 定律,它解释了为什么加机器不能无限提速:
就像炒菜时切配可以几十人分工,但颠锅只能一个人来。引擎再能加机器,调度那一瞬间的“颠锅”依然省不掉。理解了架构差异,接下来就能看懂三位厨师各自的拿手菜和软肋。
- 加速比\( S(p) \) 是使用 \( p \) 个并行节点后相对单节点的提速倍数。
- 串行部分\( 1-f \) 是任务中不可并行的比例,如任务调度、结果合并,它决定了性能天花板。
- 并行部分\( f \) 是能被多个节点共同分担的比例,节点越多这部分耗时越低。
- 极限洞察当 \( p \to \infty \) 时,加速比趋近 \( 1/(1-f) \),串行比例成了永远迈不过去的坎。
Trino 引擎
Trino 的化身是跑单小哥:你点一份“番茄炒蛋”,他箭步冲向冷库,抓来番茄和鸡蛋,三分钟上桌。但如果你点的是“满汉全席”——几十张大表 Join 的复杂查询——他怀里抱不下那么多食材,跑到一半东西撒了,只好空手而归。
Trino(源自 Presto 项目)是专为交互式查询设计的分布式 SQL 引擎。它的协调器(Coordinator)是无状态的,可以轻松横向扩展和故障恢复;Worker 则负责执行并行任务。它最亮眼的看家本领是联邦查询:一条 SQL 可以同时查询 MySQL、Hive、Iceberg、Elasticsearch 等多个数据源,就像跑单小哥能跑遍园区里所有仓库取货。这使得 Trino 成为湖仓架构中“即席分析”和“跨源关联”的首选。
Trino 的查询延迟由多个环节叠加而成,定位性能瓶颈时可以逐项拆解:
Trino 的短板在于:数据基本常驻内存处理,一旦聚合或排序的中间结果超过所有 Worker 的内存总和,查询就会报 OOM(内存溢出)错误。新版虽然支持溢写到磁盘,但默认关闭且性能远不如内存路径。所以,跑单小哥最懂“快”的哲学,也最怕“大”的订单。
- 总查询延迟\( T_{\text{query}} \) 是用户从提交 SQL 到看到结果的墙钟时间,是体验的直接指标。
- 解析规划\( T_{\text{plan}} \) 是 SQL 解析和优化器制定执行计划的时间,通常在毫秒级。
- 任务调度\( T_{\text{schedule}} \) 是 Coordinator 把拆分好的任务分发到各 Worker 的耗时。
- 数据读取\( T_{\text{io}} \) 是从远端存储读取数据的时间,大表扫描时往往是主要瓶颈。
- 计算执行\( T_{\text{cpu}} \) 是过滤、聚合、Join 等 CPU 运算时间,取决于数据规模和算子效率。
Spark 引擎
中央厨房团队的行事风格是“兵马未动,粮草先行”:凌晨先把几百公斤食材统一清洗、切配、分装,然后才轮到各灶台开火。所以它接小单反而有点慢,接大单却稳如泰山——这就是 Spark SQL 的性格画像。
Spark SQL 是 Apache Spark 面向 SQL 的入口,核心由 Catalyst 优化器和 Tungsten 执行引擎组成。Driver 把 SQL 解析成逻辑计划,经过优化后生成物理计划,再切分成多个 Stage;每个 Stage 内的任务由集群中的 Executor 并行执行,Stage 之间通过 Shuffle 跨节点交换中间数据。这个模型天然适合批量 ETL(数据清洗转换加载):读出海量数据,过滤、聚合、写出,一气呵成。
Spark 在“大”字面前非常从容——内存不够可以溢写磁盘,任务失败会自动重试。但这也让“小”查询显得不划算:Driver 启动、Stage 调度、Shuffle 洗牌都有固定开销。Shuffle 是理解 Spark 性能的关键所在,它的成本模型非常直观:
中央厨房的菜要在各操作台之间来回传递,传递的食材越多越慢。聪明的厨师会先把食材在小推车上合并(预聚合),再一次性送达。这就是为什么 Spark 工程师们总在 Shuffle 上做文章——省下的搬运时间,就是省下的真金白银。
- Shuffle 时间\( T_{\text{shuffle}} \) 是聚合或 Join 时跨节点搬运中间数据的总耗时。
- 数据规模\( V \) 是参与搬运的数据量,与分区键分布和算子预处理程度直接相关。
- 传输带宽\( B \) 是集群网络聚合带宽,千兆或万兆网卡决定了搬运速度的上限。
- 优化启示要降低 Shuffle 时间,最有效的杠杆是减小 \( V \)——提前过滤、预聚合、广播小表都能显著瘦身。
Photon 引擎
特级厨师 C 师傅的秘诀不在手艺,而在装备:他手里的“多头锅铲”一次能同时兜起八块肉,砧板自带分类格,火候由温度计实时调节。同一道菜,别人忙活三分钟,他五十秒出锅。
Photon 是 Databricks 于 2022 年发布的向量化执行引擎,用 C++ 重写了 Spark 执行层的核心算子。它不改变 Spark 的 API 和调度模型,而是让底层运算从“逐行解释执行”变成“按批次向量计算”,并对 CPU 缓存、指令级并行做了极致的优化。用户只需在 Databricks 集群上打开 Photon 开关,原有的 Spark SQL 就能自动加速,就像给中央厨房免费换了一套进口厨具。
向量化执行的核心是 SIMD(单指令多数据)——一条 CPU 指令同时处理多个数据元素,而不是一次只处理一个。加速比的理论模型如下:
需要注意的是,Photon 只运行在 Databricks 平台,主要对 Delta Lake 上的数据加速;一旦遇到自定义 UDF(用户自定义函数),它会从向量模式退回行模式,加速效果大打折扣。特级厨师再厉害,也依赖自家厨房和熟悉的食材供应链——换到陌生灶台,他的“多头锅铲”就施展不开了。
- 标量耗时\( T_{\text{scalar}} \) 是逐行解释执行的时间,每条数据都要经历指令解析和跳转。
- 向量耗时\( T_{\text{vector}} \) 是按批次批量计算的耗时,一次处理成百上千行数据。
- 理论上限\( \text{SIMD\_width} \) 是一条 CPU 指令同时处理的数据元素个数,如 AVX2 指令集对应 8 个 32 位整数。
- 现实折扣内存带宽、分支预测、UDF 回退都会拉低实际加速比,工程上常见 1.8 倍到 8 倍。
性能基准对比
大赛评委端来同一桌菜谱,要求三位厨师分别做出一整桌宴席,记录总时间和每道菜用时。结果发现:跑单小哥做凉菜快得惊人,但遇到需要慢火炖的大菜直接掀桌;中央厨房每道菜都四平八稳,总时长中规中矩;特级厨师做凉菜热菜都最快。
在数据领域,这套“宴席标准”就是 TPC-DS 基准测试——一套模拟真实决策支持负载的 99 条复杂 SQL,涵盖多表 Join、子查询、窗口函数等场景。评估引擎综合实力时,业界不用算术平均,因为少数慢查询会严重拉偏总分;更公平的做法是几何平均,它让每条查询都有平等的“投票权”:
工程实践的画像大致如下:交互式小查询(几秒内)中,Trino 与 Photon 接近,都明显快于原生 Spark;大规模批处理 ETL 中,Spark 和 Photon 的吞吐更高,Trino 容易内存不足;Photon 在 Delta Lake 上综合领先。成本层面,Photon 通过减少集群节点数压低账单,但软件许可与平台绑定必须计入总成本。选厨师不能只看“哪道菜最快”,要看你的宴席是什么风格——凉菜多选跑单小哥,包席量大选中央厨房,追求极致出餐效率且食材标准统一,选特级厨师。
- 查询样本\( Q_i \) 是第 \( i \) 条查询的执行时间,代表一个具体业务场景的响应速度。
- 几何平均\( GM \) 是 n 个耗时乘积的 n 次方根,任何一条慢查询都会被计入,但不会被极端值绑架。
- 对数等价\( \exp \) 与 \( \ln \) 的写法便于程序稳定计算,避免连乘溢出。
- 对比含义对同样的 SQL 集合,\( GM \) 越小,引擎整体性能越强。
优化策略
无论是哪支厨师团队,好的后厨管理都有共通法则:把食材按类型分格存放、把用不上的边角料提前处理掉、减少操作台之间来回跑动。这些“后厨管理术”对应着大数据领域的经典优化手段,而且往往比换一个引擎更立竿见影。
查询慢常常不是引擎不行,而是数据布局太乱。最有效的三板斧:分区裁剪、谓词下推、数据布局排序。分区裁剪让引擎只读必要的分区,省掉大量无谓扫描。它的收益可以用一个简单的比例衡量:
除了分区,谓词下推把 WHERE 过滤条件下沉到 Parquet/ORC 读取层,直接跳过无关行组;Z-Order 排序让相似数据在文件内聚簇,配合列存统计信息实现跳跃读取;小文件治理则把大量几 MB 的碎片文件合并成 128MB 以上的大文件,减轻元数据压力与任务调度开销。冷库里每个货架都分好类、贴好标签,厨师报出菜名,帮工直奔对应货架端来食材——这就是分区与下推的智慧;把零碎香料合并成标准调料罐,就是小文件治理。
- 有效扫描比\( \text{Scan\_ratio} \) 是实际读取的数据量占全表数据的比例。
- 命中分区\( \text{Scan\_partitions} \) 是经过 WHERE 条件过滤后仍需读取的分区数量。
- 总分区数\( \text{Total\_partitions} \) 是整张表划分的全部分区数量。
- 收敛效果按日期分区后查询“昨天”的数据,只需读取 1/365 的分区,扫描比例降到 0.3% 左右。
AI 应用场景
场景一:跨源实时 BI 报表(Trino)
阶段:数据探索与交互式分析
解决的问题:业务分析师既要查 MySQL 订单表,又要查数据湖中的行为日志。传统做法是先定时同步到数仓再出报表,数据延迟高,且需要维护两套连接。Trino 作为“跑单小哥”,一条 SQL 即可联邦查询多个数据源,实现秒级响应。
具体案例:某跨境电商团队用 Trino 连接 RDS 交易库与 Iceberg 湖,实时看板“今日 GMV 与用户活跃”直接从两个源 Join,报表从 T+1 延迟缩短到秒级,分析师自助取数不再排队。
场景二:离线批量特征管道(Spark SQL)
阶段:数据加工与批处理
解决的问题:ODS 层每天新增数百 GB 原始日志,需要清洗、去重、打宽表后供特征工程使用。交互式引擎扛不住这种吞吐压力,Spark SQL 作为“中央厨房”,通过分布式批处理和容错机制稳定扛下大规模任务。
具体案例:某推荐系统团队每天凌晨用 Spark SQL 运行特征管道:读取 Kafka 落地的行为日志,做 Session 切分、统计特征聚合,写回 Delta 表,供白天的模型训练与在线查询使用。
场景三:Delta 湖上查询提速(Photon)
阶段:查询加速与性能优化
解决的问题:BI 查询在 Delta 表上耗时 30 秒以上,加节点扩容成本高。开启 Photon 后无需修改任何 SQL,向量化执行引擎自动接管运算,显著降低延迟与集群资源消耗。
具体案例:某 SaaS 公司用 Databricks 跑月度经营分析,开启 Photon 后复杂报表平均提速 3.5 倍,集群从 20 台缩减到 12 台,季度账单下降 40%。
Python 演示
import math class Engine: def __init__(self, name, fixed_sec, per_gb_sec, mem_limit_gb): self.name = name self.fixed_sec = fixed_sec self.per_gb_sec = per_gb_sec self.mem_limit_gb = mem_limit_gb def query_time(self, data_gb): if data_gb > self.mem_limit_gb: return None # OOM 内存不足 查询失败 return self.fixed_sec + self.per_gb_sec * data_gb def geomean(times): logs = [math.log(t) for t in times if t is not None] return math.exp(sum(logs) / len(logs)) engines = [ Engine("Trino", fixed_sec=0.5, per_gb_sec=1.0, mem_limit_gb=50), Engine("SparkSQL", fixed_sec=5.0, per_gb_sec=0.4, mem_limit_gb=200), Engine("Photon", fixed_sec=2.0, per_gb_sec=0.15, mem_limit_gb=200),] sizes = [10, 20, 40, 80, 160]for e in engines: times = [] for s in sizes: t = e.query_time(s) if t is None: times.append(999) print(f"{e.name}: {s}GB 查询失败 OOM") else: times.append(t) print(f"{e.name}: {s}GB -> {t:.1f}s") gm = geomean(times) print(f"{e.name}: 几何平均 {gm:.2f}s") print()
class Engine:用一个类抽象三种查询引擎,参数差异代表引擎的架构特点。if data_gb > self.mem_limit_gb: return None模拟内存超限时查询失败,对应 Trino 大查询的 OOM 风险。return self.fixed_sec + self.per_gb_sec * data_gb耗时模型:固定开销加随数据量线性增长的部分。Engine("Trino", fixed_sec=0.5, per_gb_sec=1.0, mem_limit_gb=50)Trino 启动快但单 GB 成本高、内存上限小。logs = [math.log(t) for t in times if t is not None]对耗时取对数,为几何平均做准备,失败查询记为大值。
常见误区
误区听说 Trino 是秒级响应,那把 ETL 和报表都换成 Trino 跑,是不是就能彻底告别慢查询了?
正解Trino 的“快”指的是交互式小查询场景。它把数据拉进内存计算,大表 Join 时容易内存溢出;Spark 通过溢写磁盘和任务重试把超大任务稳稳扛住。选引擎要看负载类型,跑单小哥再快也接不了满汉全席。
为何常见:人们习惯用“响应快”这一个维度衡量引擎,忽略了数据规模、稳定性和容错能力等关键因素。
误区Photon 听起来和 Presto 一样是个独立引擎,是不是可以单独下载部署,接上自己的数据就能用?
正解Photon 不是独立引擎,而是 Databricks 平台内 Spark 执行层的向量化加速组件。它必须运行在 Databricks 环境并配合 Delta Lake 才能发挥最大价值,无法脱离平台单独使用。
为何常见:厂商宣传总强调“提速 N 倍”,容易让人误以为它是一个可以随处插拔的独立产品。
误区我把数据都放到 S3 上用 Parquet 格式存储,这不就是湖仓一体了吗?
正解只有文件格式远远不够。湖仓一体还需要 ACID 事务、Schema 强制、索引和元数据管理等能力,这些由 Delta Lake、Iceberg、Hudi 等表格式提供。否则只是“数据沼泽”,连改个文件名都可能破坏数据一致性。
为何常见:概念走红后,很多人把“数据湖”和“湖仓一体”画上等号,忽略了中间那一层关键的表格式治理能力。
误区Photon 官方 Demo 里查询提速好几倍,那我打开开关,所有 Spark SQL 是不是都能享受同等加速?
正解Photon 对扫描、过滤、聚合、Join 等标准算子提升明显,但遇到自定义 UDF 或特殊表达式时会退回行模式,甚至出现性能回退。实际加速比通常在 1.8 到 8 倍之间,具体取决于工作负载是否“吃”向量化。
为何常见:Demo 展示的都是最理想的场景,用户误把上限当成平均值。
误区我的 Spark SQL 跑得很慢,是不是换个更快的新引擎就能彻底解决?
正解很多时候瓶颈来自数据倾斜、小文件过多、缺少分区裁剪等数据治理问题。同样的 SQL,先做分区和文件合并,提升往往比换引擎更明显。引擎只是锅,菜好不好吃还得看食材切得好不好。
为何常见:人总是倾向于找“银弹”方案,而不是做枯燥的数据治理和调优。
面试题
Q1. 湖仓一体(Lakehouse)的核心特征是?
- A.只存储非结构化文件
- B.数据湖上提供事务治理← 正确答案
- C.完全替代传统数据仓库
- D.不支持任何 SQL 查询
答案:B。湖仓一体的核心是在数据湖的低成本存储之上叠加 ACID 事务、Schema 强制、索引等“仓库级”能力,对应后厨给冷库加装管理系统。A 只描述了数据湖,C 太绝对,D 完全错误。
Q2. Trino/Presto 最擅长哪种场景?
- A.小时级 ETL 批处理任务
- B.交互式跨源联邦查询← 正确答案
- C.大规模机器学习训练
- D.亚秒级实时流计算
答案:B。Trino 的定位是交互式联邦查询,像跑单小哥一样轻装快跑,一个 SQL 查多个数据源是它的招牌。A 是 Spark 的强项,C 属于训练框架,D 属于流处理引擎。
Q3. Spark SQL 的本质定位是?
- A.通用联邦查询引擎
- B.分布式批处理引擎← 正确答案
- C.单机内存数据库
- D.非关系型文档数据库
答案:B。Spark SQL 是分布式批处理引擎的 SQL 接口,擅长海量数据的离线加工与 ETL,如同后厨的中央厨房。A 是 Trino,C、D 与分布式批处理定位无关。
Q4. Photon 大幅提速的主要原因?
- A.使用 GPU 替代 CPU
- B.C++ 向量化批量执行← 正确答案
- C.增加集群物理节点
- D.压缩存储的数据量
答案:B。Photon 用 C++ 重写执行层,按批次进行 SIMD 向量化运算,而非逐行解释执行,就像多头锅铲一次翻动八块肉。A 不对,Photon 用的是 CPU 的 SIMD 指令;C 是扩容;D 是存储优化。
Q5. 关于 Spark Shuffle 的正确说法?
- A.网络带宽越高传输越快← 正确答案
- B.减少中间数据可降低成本← 正确答案
- C.Shuffle 只在单机执行
- D.预聚合能减少数据量← 正确答案
答案:ABD。Shuffle 时间 = 数据量 / 带宽,A、B 正确;C 错误,Shuffle 发生在跨节点的网络传输中,就像操作台之间传菜;D 正确,预聚合(combiner)先在小范围内合并,显著减少传输量。
Q6. 减少查询扫描量的有效手段?
- A.分区裁剪跳过无关文件← 正确答案
- B.谓词下推到存储层← 正确答案
- C.数据倾斜加盐分区
- D.Z-Order 聚簇排序← 正确答案
答案:ABD。分区裁剪只读命中分区,谓词下推让过滤在存储层提前执行,Z-Order 让相似数据聚簇、配合列存跳读,这三者都能减少扫描量。C 数据倾斜加盐是均衡计算负载,并不减少读取的数据量。
Q7. 关于 Spark SQL 执行模型的正确说法?
- A.Driver 负责任务调度← 正确答案
- B.Stage 间通过 Shuffle 传递← 正确答案
- C.只支持批量处理模式
- D.Catalyst 生成执行计划← 正确答案
答案:ABD。Driver 统一调度任务,Stage 之间通过 Shuffle 交换中间数据,Catalyst 优化器负责生成并优化执行计划,都对。C 错误,Spark 通过 Structured Streaming 也支持流式处理,并非只能批处理。
Q8. Amdahl 定律指出并行加速的极限由什么决定?
- A.任务可并行比例 f← 正确答案
- B.所有节点内存总和
- C.数据压缩算法效率
- D.磁盘每秒读写次数
答案:A。Amdahl 定律说明加速比极限是 1/(1-f),串行部分决定了性能天花板。就像炒菜时切配可多人分工,但颠锅只能一个人来,这个“颠锅”就是不可并行的 f 的来源。
Q9. TPC-DS 对比用几何平均而非算术平均的原因?
- A.避免极端查询时间主导← 正确答案
- B.数学实现过程更简单
- C.让整体成绩显得更好
- D.方便隐藏失败查询记录
答案:A。几何平均对极端值不敏感,任何一条查询过慢都会被计入总分,但不会被少数极端值绑架整体印象。算术平均会被一条超慢查询严重拉偏,相当于评委因为一道菜失败就否定整桌宴席。
Q10. 在 Photon 上运行含自定义 UDF 的 SQL 时会怎样?
- A.自动转为向量化代码
- B.退回行模式加速失效← 正确答案
- C.直接拒绝执行任务
- D.秒级报错终止查询
答案:B。Photon 无法识别任意自定义 UDF(用户自定义函数),遇到时会从向量化批量模式退回逐行模式,导致加速效果大幅缩水。它不会自动改写 UDF,也不会拒绝或报错,而是“降级”运行,就像特级厨师遇到不熟悉的食材,只能改用普通锅铲慢慢做。