在过去的十年里,数据平台的发展像极了搬家。一开始我们只有一个小箱子,后来箱子越来越多,最后不得不租一个仓库,再后来发现仓库里堆的东西太乱,找东西要翻半天。于是有人提出,干脆把常用的东西放在客厅,不常用的丢进地下室。这就是数据仓库和数据湖最初的分工。可问题在于,客厅和地下室之间隔着一堵墙,搬来搬去很费劲。今天我们要聊的就是,Google 的 BigQuery 在这堵墙上开了一扇门,让数据湖里的冷数据也能被当成仓库里的热货来用,这套思路就是现在大家常说的“湖仓一体”。

一、从两座孤岛说起:数据仓库和数据湖的由来

1.1 数据仓库:请客吃饭讲规矩

数据仓库就像一家讲究的餐厅。食材必须清洗切好,按菜单分门别类,客人点菜的时候,后厨可以立刻下锅。它的特点是强结构、强一致、擅长处理复杂的分析查询。比如银行的报表、电商的销售统计,这些业务数据量虽然不小,但格式整齐,用实时数仓处理又快又准。

1.2 数据湖:先囤货再说

数据湖更像是自由市场。不管你是卖菜的、修鞋的,还是算命的,都可以进来摆摊。这里什么都有,原始日志、图片、视频、传感器数据,乱七八糟但胜在便宜。没有数据仓库那么严格的建模要求,存储成本极低。可问题是,想要在里面找点有用的东西做分析,往往要自己写一堆加工程序,速度慢,效率也低。

这两者本来是各干各的,但现实是,一家公司不可能只拥有其中一种。数据仓库里放着核心业务数据,数据湖里躺着各种原始文件。想做一份综合报表,就得把两边的数据搬来搬去,既贵又麻烦。

二、BigQuery 的身份:天生就是数据仓库

BigQuery 是 Google Cloud 上的一款云原生数据仓库。你可以把它理解成一家“开了外挂的餐厅”,不用你自己买锅碗瓢盆,厨房设备全是服务商提供的,你只需要点菜就行。它有几个很吸引人的特点:

  • 自动扩展计算资源,查询再大也不用担心集群不够用。
  • 按量计费,查询多少数据付多少钱,不查就不花钱。
  • 内置大量分析函数,支持机器学习、地理信息等高级功能。

以前我们用 BigQuery,数据必须导入到它自己的存储里。也就是说,你得先搬货,再吃饭。数据一旦进了 BigQuery,就成了“仓库的正式员工”,有编制、有工位,但随之而来的就是存储费用和管理成本。

三、外部表和 BigLake 到底改变了什么

3.1 什么是外部表

简单说,外部表就是站在数据湖的文件上,假装自己是数据仓库里的表。BigQuery 可以直接读取存放在 Google Cloud Storage(简称 GCS)上的 CSV、Parquet、Avro、JSON 等文件,而不需要把文件导入到 BigQuery 内部存储里。

来看一个最基本的例子,我们有一批推送日志放在 GCS 的某个桶里,想用 BigQuery 直接查:

-- 创建一张外部表,直接指向云存储里的 Parquet 文件
CREATE OR REPLACE EXTERNAL TABLE `my_project.my_dataset.push_logs_external`
OPTIONS (
  format = 'PARQUET',                                  -- 文件格式是 Parquet
  uris  = ['gs://my-bucket/push_logs/*.parquet']       -- 云存储路径,支持通配符
);

创建之后,你就可以用普通的 SELECT 去查它:

-- 像查普通表一样查询外部表
SELECT
  user_id,
  COUNT(*) AS push_count
FROM `my_project.my_dataset.push_logs_external`
WHERE event_date = '2025-03-20'
GROUP BY user_id
ORDER BY push_count DESC
LIMIT 10;

是不是很方便?但外部表有一个问题:它没有对文件做统一管理,文件格式变化、分区信息、数据一致性都得不到保障。比如你今天放了一批 Parquet,明天又有人放了几份 CSV 混在里面,查询就会报错。分区剪裁也做不好,扫全桶的情况很常见,费钱还慢。

3.2 BigLake 登场

BigLake 是 BigQuery 在外部表基础上的一次全面升级。它本质上仍然是一张“挂”在数据湖文件上的表,但它引入了数据仓库级别的元数据管理、分区优化、权限控制,甚至还能让其他计算引擎(比如 Spark)通过 BigQuery 的元数据来读写同一个湖里的数据。

更直白一点说,BigLake 就是一座桥,把数据仓库的“管理能力”和对象存储的“廉价容量”接在了一起。文件还在 GCS 上,但 BigQuery 可以像管理自家表一样管理它。

3.3 用一段 SQL 看清 BigLake 表和外部表的区别

-- 创建 BigLake 表,除了指向 GCS,还声明了分区字段和聚簇字段
CREATE OR REPLACE TABLE `my_project.my_dataset.push_logs_biglake`
(
  user_id      STRING,                                  -- 用户 ID
  event_time   TIMESTAMP,                               -- 事件时间
  event_type   STRING,                                  -- 事件类型
  push_context JSON                                     -- 推送上下文,存JSON
)
PARTITION BY DATE(event_time)                           -- 按天分区
CLUSTER BY user_id                                      -- 按用户ID聚簇
OPTIONS (
  format = 'PARQUET',                                   -- 底层数据格式
  uris = ['gs://my-bucket/push_logs/*.parquet'],        -- 数据文件位置
  hive_partition_uri_prefix = 'gs://my-bucket/push_logs/' -- 分区前缀
);

注意这张表用的是 CREATE TABLE,而不是 CREATE EXTERNAL TABLE。它的分区和聚簇信息被记录了下来,查询时 BigQuery 会智能跳过无关文件,成本降低很多。更重要的是,它支持对数据湖里的文件做 ACID 语义管理,防止读到一半文件被删除之类的尴尬。

四、BigQuery 在湖仓一体架构中的真正角色

4.1 它不再是一个“仓库”,而是“大脑”

如果我们把整个数据平台比作一家公司,GCS 数据湖是仓库,BigQuery 就是决策中枢。以前 BigQuery 只管自己仓库里的熟食,现在通过 BigLake 和外部表,它可以直接指挥仓库里的生鲜食材。分析人员不必关心数据究竟存放在哪里,甚至不需要知道底层是文件还是内部存储。BigQuery 统一了查询入口,这就是湖仓一体的核心体验:一个入口,访问所有数据。

4.2 存储与计算彻底分离的落地形态

传统数据仓库的存储和计算是绑定的,扩容要一起扩,缩容也要一起缩,容易浪费钱。BigQuery 天然就是把存储和计算分开的,它把数据放在分布式文件系统上,计算资源按需伸缩。引入 BigLake 后,这个分离更进一步:数据直接存放在客户自己的 GCS 桶中,连 BigQuery 内部存储都不用了。如此一来:

  • 存储成本由 GCS 管理,可以配置冷热存储策略,把半年以前的数据自动转成低频访问甚至归档存储。
  • 计算资源由 BigQuery 管理,高峰期自动扩展,低峰期归零。
  • 数据格式保持开源生态,比如 Parquet、Avro,以后就算不想要 BigQuery 了,也能用 Spark、Presto 直接读同一份文件。

下面这张流程图对应的就是这种架构(用 SQL 描述):

-- 模拟一个湖仓一体的查询流程:
-- 1. 用 BigLake 表读取 GCS 里的原始日志
-- 2. 用普通 CTE 做清洗
-- 3. 把聚合结果写入 BigQuery 内部高分区表供报表使用

WITH raw_logs AS (
  -- 从 BigLake 表读取最近7天日志
  SELECT * FROM `my_project.my_dataset.push_logs_biglake`
  WHERE DATE(event_time) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
),
cleaned_logs AS (
  -- 去掉 user_id 为空的脏数据
  SELECT *
  FROM raw_logs
  WHERE user_id IS NOT NULL
)
-- 将结果写入一个内部表,便于后续快速查询
INSERT INTO `my_project.my_dataset.daily_push_summary`
SELECT
  DATE(event_time) AS dt,
  event_type,
  COUNT(DISTINCT user_id) AS active_users,
  COUNT(*) AS total_events
FROM cleaned_logs
GROUP BY dt, event_type;

注意,前面用的是 BigLake 表做源,后面写进的是 BigQuery 内部表。这在生产环境里非常常见:湖里屯着原始数据,仓库里放着加工好的汇总数据,两者通过 BigQuery 的计算能力协同工作。

五、一个实战场景:把日志数据交给 BigQuery 和 BigLake 管

假设你是一个 App 的开发者,每天产生上亿条用户行为日志,存在 GCS 的 user_logs/ 目录下。以前你需要把每天的数据用 Dataflow 导入 BigQuery,麻烦且费钱。现在你可以直接建一张 BigLake 表,让 BigQuery 去读云上的文件,再按天做分区。

第一步,确认目录结构:

gs://my-company-logs/user_logs/dt=2025-03-20/part-00001.parquet
gs://my-company-logs/user_logs/dt=2025-03-20/part-00002.parquet
gs://my-company-logs/user_logs/dt=2025-03-21/part-00001.parquet

第二步,创建 BigLake 表:

-- 创建BigLake表,注意使用了hive分区目录结构
CREATE OR REPLACE TABLE `my_project.log_analysis.user_logs_biglake`
(
  user_id     STRING,                                -- 用户ID
  page_url    STRING,                                -- 访问页面
  action      STRING,                                -- 行为类型
  ts          TIMESTAMP                              -- 时间戳
)
PARTITION BY DATE(ts)                                -- 时间分区
OPTIONS (
  format = 'PARQUET',
  uris = ['gs://my-company-logs/user_logs/*.parquet'],
  hive_partition_uri_prefix = 'gs://my-company-logs/user_logs/'
);

第三步,写一个典型的分析查询,统计最近 7 天每个页面的 UV:

-- 统计最近7天每个页面的访问用户数
SELECT
  page_url,
  COUNT(DISTINCT user_id) AS uv
FROM `my_project.log_analysis.user_logs_biglake`
WHERE DATE(ts) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
GROUP BY page_url
ORDER BY uv DESC
LIMIT 20;

第四步,如果发现数据文件越来越多,我们只想保留近 30 天的快节奏查询,更早的数据切到成本更低的存储等级。这个是存储桶层面的配置,不在 BigQuery 内操作,但 BigQuery 的 BigLake 表不会受影响,因为文件路径没变。这就是存储计算分离带来的灵活性。

六、技术优缺点

6.1 优点

第一,成本优势明显。数据不占用 BigQuery 内部存储,GCS 的价格远低于集群存储。对于海量日志类数据,省下的是真金白银。

第二,不用搬数据。直接在数据湖文件上建表,省去了定时导入流程。原来抽数据可能要几小时,现在建张表几秒钟。

第三,查询性能可控。BigLake 支持分区剪裁、聚簇过滤,虽然不如完全内部存储那么快,但已经能把扫描控制在很小的范围。配合 BigQuery 的缓存机制,日常分析完全可以接受。

第四,权限统一管理。用 BigQuery 的权限模型控制谁可以查数据湖里的文件,不用给每个人开 GCS 访问权限。这个对于企业数据安全特别关键。

6.2 缺点

BigLake 表的查询性能依然达不到 BigQuery 内部表的水平。如果你需要秒级交互式查询,最好把结果物化到内部表。

元数据同步有时会滞后。如果数据湖文件频繁变更又没触发更新机制,会发现曲线读出来不够新鲜。通常需要配合 Cloud Run 或 Eventarc 触发去刷新元数据。

还有就是对 SQL 方言有一定限制。因为底层文件存储是开放的,像 MERGE 这种运维操作并不支持在所有表上执行。数据更新要么走重写文件,要么走外部工具,比普通数据仓库繁琐。

七、注意事项

在实际项目中,有几个坑千万要避开:

文件格式尽量统一。别今天用 Parquet,明天用 Avro,后天突然混入几个 CSV。BigLake 表虽然有 schema 推断能力,但不同格式的隐含类型差异会导致查询报错或结果不一致。

分区目录的命名必须规范。Hive 风格的分区目录是事实标准,比如 dt=2025-03-20,不要变成 2025-03-20 这种裸目录,否则分区剪裁不生效,每次全表扫描直接烧钱。

不要在 BigLake 表上做过于复杂的 JOIN 和大型聚合。它本质是读文件,把计算下推的能力有限。对于多表大关联,不如把数据先物化到内部表再查询。

还有一点很现实:权限和网络。BigLake 表连接 GCS 通常需要配置服务账号和权限。如果数据在另一个项目或者是跨区域,就要考虑网络出口费用。跨区域读 GCS 会产生额外流量费,最好将 BigQuery 和 GCS 放在同一个 region。

八、总结

BigQuery 在湖仓一体架构中不是一个“仓储系统”,而是一个“统一接口”和“计算引擎”。数据湖负责存储海量文件,BigQuery 负责让这些文件变得可查询、可治理、可分析。BigLake 表和外部表是这层连接的关键技术。外部表解决了能不能查的问题,BigLake 解决了好不好查和可不可信的问题。

对于大多数中小团队来说,直接使用 BigQuery + GCS + BigLake 已经能覆盖八成以上的数据分析场景。不需要自己搭建 Spark 集群,不需要维护 Hive Metastore,也能享受到数据湖的低成本和数据仓库的管控能力。这是一条实实在在落地湖仓一体的捷径。

当你在设计自己的弹性数据平台时,不妨先把数据湖的目录结构规划好,然后建出 BigLake 表,再从报表需求倒推内部物化视图。用 BigQuery 做大脑,让数据湖变成你的冷藏室,这种感觉还挺舒服的。