1.1 从业务痛点理解数仓价值
现实场景:一个零售公司的数据分析困境
背景: 某中型零售公司"优品生活",拥有线上商城、线下门店、微信小程序三个销售渠道,使用不同的业务系统。
四个典型数据困境:
困境一:报表系统为什么越来越慢?
sql
-- 业务人员想分析"昨日各门店销售额",需要查询生产数据库
SELECT store_name, SUM(order_amount)
FROM orders
JOIN stores ON orders.store_id = stores.id
WHERE DATE(order_time) = '2024-01-15' -- 昨日
GROUP BY store_name;
问题:
- 订单表有5000万条记录,每次查询全表扫描
- 与门店表关联时影响线上订单处理
- 财务、市场、运营都在同一时间跑报表,数据库CPU飙升至90%
根本原因: OLTP(在线事务处理)系统不是为分析查询设计的
困境二:部门数据为什么对不上?
每月销售会议上的尴尬对话:
- 财务总监: "1月线上销售额是2850万"
- 市场总监: "不对,我们统计是3120万"
- 运营总监: "我们系统显示2990万"
数据不一致的原因:
三个系统,三种计算逻辑:
1. 财务系统:已收款订单(剔除退款)
2. 市场系统:所有下单金额(包括未支付)
3. 运营系统:已发货订单(不包括取消)
三个系统,三种数据定义:
1. "北京朝阳门店"在财务系统编码:BJ-CY-001
2. 在市场系统:STORE_110105
3. 在运营系统:北京1号店
困境三:数据分析为什么要等一天?
业务需求: "老板要看今天上午的销售实时情况"
技术现实:
08:00 - 业务系统产生新订单
09:00 - 第一次数据同步(凌晨同步还在进行)
12:00 - 凌晨同步完成,开始上午同步
14:00 - 数据清洗转换
16:00 - 数据加载到报表系统
17:00 - 业务人员终于看到数据
等待链条:
订单产生 → 业务系统 → 隔夜同步 → ETL处理 → 报表更新
(实时) (实时) (T+1) (2小时) (1小时)
困境四:为什么需要历史数据分析?
业务场景: "分析去年春节和今年春节的销售对比"
生产数据库的局限性:
sql
-- 生产数据库为了性能,通常会:
-- 1. 删除历史数据(只保留3个月)
-- 2. 修改用户信息(覆盖旧地址)
-- 3. 更新商品价格(没有历史记录)
-- 无法回答的问题:
-- "这个客户三年前的购买偏好是什么?"
-- "这个商品价格是如何变化的?"
-- "促销活动的长期效果如何?"
数据仓库的解决方案
针对上述四个困境,数仓提供了系统化的解决方案:
| 业务痛点 | 数仓解决方案 | 技术实现 |
|---|---|---|
| 报表慢 | 分析查询与事务处理分离 | 独立的OLAP系统 + 列式存储 |
| 数据不一致 | 建立企业单一事实源 | ETL统一清洗 + 标准维度 |
| 分析滞后 | 建立合理的数据更新频率 | 增量同步 + 分层处理 |
| 缺乏历史 | 保存完整的时态数据 | 缓慢变化维 + 历史表 |
1.2 数仓的四大核心特性
特性一:面向主题(Subject-Oriented)
传统系统 vs 数仓的数据组织方式
传统业务系统的数据组织(按功能划分):
┌─────────────────────────────────────┐
│ ERP系统(按模块) │
├─────────┬─────────┬─────────┬───────┤
│ 采购模块 │ 库存模块 │ 销售模块 │ 财务模块│
│ (采购单) │ (库存量) │ (销售单) │ (总账) │
└─────────┴─────────┴─────────┴───────┘
问题:分析"产品盈利能力"需要跨4个模块查询
数据仓库的数据组织(按主题划分):
┌─────────────────────────────────────┐
│ 数据仓库(按业务主题) │
├──────────────┬────────────┬─────────┤
│ "销售"主题 │ "客户"主题 │"产品"主题│
├──────────────┼────────────┼─────────┤
│ • 订单事实 │ • 客户维度 │ • 产品维度│
│ • 退货事实 │ • 地址维度 │ • 分类维度│
│ • 促销事实 │ • 分层维度 │ • 供应商维│
└──────────────┴────────────┴─────────┘
优势:分析"产品盈利能力"在一个主题内完成
主题域划分的实战示例
sql
-- 零售公司的典型主题域
CREATE SCHEMA sales; -- 销售主题:订单、退货、促销
CREATE SCHEMA customer; -- 客户主题:画像、行为、价值
CREATE SCHEMA product; -- 产品主题:分类、库存、成本
CREATE SCHEMA finance; -- 财务主题:收入、成本、利润
CREATE SCHEMA supply_chain; -- 供应链主题:采购、物流、库存
-- 每个主题包含:
-- 1. 核心事实表(发生了什么)
-- 2. 相关维度表(在什么背景下发生的)
-- 3. 聚合汇总表(不同粒度的汇总)
特性二:集成性(Integrated)
数据集成:从"方言"到"普通话"
集成前的混乱状态:
sql
-- 系统A(电商平台):用户状态
SELECT user_status FROM users; -- 返回:'ACTIVE', 'INACTIVE'
-- 系统B(CRM系统):客户状态
SELECT customer_state FROM customers; -- 返回:1, 2, 0
-- 系统C(客服系统):用户活跃度
SELECT active_flag FROM clients; -- 返回:'Y', 'N'
-- 三个系统,同一概念,三种表示!
数据仓库的统一集成:
sql
-- ETL转换过程示例
INSERT INTO dim_customer (customer_key, customer_status)
SELECT
-- 生成统一代理键
MD5(COALESCE(user_id::TEXT, customer_id::TEXT)) AS customer_key,
-- 统一状态编码转换
CASE
WHEN user_status = 'ACTIVE' THEN '活跃'
WHEN customer_state = 1 THEN '活跃'
WHEN active_flag = 'Y' THEN '活跃'
WHEN user_status = 'INACTIVE' THEN '不活跃'
WHEN customer_state IN (0, 2) THEN '不活跃'
WHEN active_flag = 'N' THEN '不活跃'
ELSE '未知'
END AS customer_status
FROM
-- 跨系统数据源
source_system_a.users
FULL OUTER JOIN source_system_b.customers ON ...
FULL OUTER JOIN source_system_c.clients ON ...
集成性的四个维度
| 集成维度 | 内容 | 示例 |
|---|---|---|
| 命名标准化 | 统一字段、表名规范 | user_id → customer_id |
| 编码统一化 | 统一代码值含义 | 1=活跃,0=不活跃 |
| 格式规范化 | 统一数据类型格式 | 手机号:13800138000 |
| 业务规则一致 | 统一计算逻辑 | 销售额=实收金额-退款 |
特性三:非易失性(Non-Volatile)
为什么数据仓库不可修改?
业务数据库 vs 数据仓库的写入模式对比:
sql
-- OLTP系统(可修改)
UPDATE orders SET status = 'CANCELLED' WHERE order_id = 1001;
DELETE FROM cart_items WHERE user_id = 5002;
-- 特点:支持增、删、改,数据反映当前状态
-- OLAP系统(不可修改)
INSERT INTO fact_orders VALUES
(1001, '2024-01-15', 'COMPLETED', 299.00),
(1001, '2024-01-16', 'CANCELLED', 299.00); -- 新增一条取消记录
-- 特点:只支持插入,保留所有历史状态
时态数据存储的三种模式
sql
-- 模式1:快照表(每日全量)
CREATE TABLE customer_snapshot (
snapshot_date DATE,
customer_id INT,
customer_name VARCHAR(100),
customer_status VARCHAR(20),
total_orders INT,
total_amount DECIMAL(10,2)
);
-- 每天保存所有客户的完整状态
-- 优点:查询历史任意一天状态简单
-- 缺点:存储空间大
-- 模式2:流水表(仅变化)
CREATE TABLE customer_status_history (
customer_id INT,
old_status VARCHAR(20),
new_status VARCHAR(20),
change_time TIMESTAMP,
change_reason VARCHAR(200)
);
-- 只记录状态变化
-- 优点:存储效率高
-- 缺点:查询历史状态需要回溯计算
-- 模式3:拉链表(最常用)
CREATE TABLE customer_chain (
customer_id INT,
customer_status VARCHAR(20),
start_date DATE,
end_date DATE,
is_current BOOLEAN
);
-- 示例数据:
-- | customer_id | status | start_date | end_date | is_current|
-- |-------------|--------|------------|------------|-----------|
-- | 1001 | 新客户 | 2024-01-01 | 2024-01-14 | false |
-- | 1001 | 活跃 | 2024-01-15 | 2024-03-31 | false |
-- | 1001 | 流失 | 2024-04-01 | 9999-12-31 | true |
特性四:时变性(Time-Variant)
时间维度的三个层次
sql
-- 1. 数据本身的时间属性(业务时间)
CREATE TABLE fact_orders (
order_key BIGINT,
customer_key INT,
product_key INT,
-- 业务时间维度
order_date DATE, -- 下单日期
ship_date DATE, -- 发货日期
payment_date DATE, -- 付款日期
-- 技术时间维度
load_date DATE, -- 加载到数仓日期
effective_date DATE, -- 记录生效日期
expiry_date DATE, -- 记录失效日期
-- 业务度量
order_amount DECIMAL(10,2),
quantity INT
);
-- 2. 时间作为分析维度(时间维度表)
CREATE TABLE dim_date (
date_key INT PRIMARY KEY, -- 20240115
full_date DATE, -- 2024-01-15
day_of_month INT, -- 15
month_name VARCHAR(20), -- January
quarter INT, -- 1
year INT, -- 2024
is_weekend BOOLEAN, -- false
is_holiday BOOLEAN, -- false
holiday_name VARCHAR(50) -- NULL
);
-- 3. 历史数据分析能力
-- 能回答的问题示例:
-- "比较今年和去年同期的销售增长率"
-- "分析促销活动对后续三个月销售的影响"
-- "识别客户生命周期中的关键转化点"
时变性的业务价值体现
场景:分析产品价格调整的市场反应
sql
-- 生产系统只能看到当前价格
SELECT product_id, current_price FROM products;
-- 结果:product_001 → 299元
-- 数据仓库能看到完整价格历史
SELECT
p.product_name,
ph.old_price,
ph.new_price,
ph.change_date,
-- 分析价格变化后的销量变化
SUM(CASE
WHEN o.order_date BETWEEN ph.change_date AND ph.change_date + 7
THEN o.quantity ELSE 0
END) as week_after_sales,
SUM(CASE
WHEN o.order_date BETWEEN ph.change_date - 7 AND ph.change_date - 1
THEN o.quantity ELSE 0
END) as week_before_sales
FROM product_price_history ph
JOIN dim_product p ON ph.product_key = p.product_key
LEFT JOIN fact_orders o ON p.product_key = o.product_key
GROUP BY p.product_name, ph.old_price, ph.new_price, ph.change_date
ORDER BY ph.change_date DESC;
-- 能够发现:价格从299降到249时,销量增长40%
-- 价格从249升回299时,销量下降25%
1.3 数仓 vs 业务数据库 vs 数据湖
三者的本质区别
| 维度 | 业务数据库 (OLTP) | 数据仓库 (OLAP) | 数据湖 |
|---|---|---|---|
| 主要用途 | 日常业务操作 | 数据分析决策 | 原始数据存储 |
| 数据结构 | 高度结构化 | 结构化 | 任意结构 |
| 数据新鲜度 | 实时 | 分钟~天级延迟 | 实时~批处理 |
| 数据粒度 | 明细事务级 | 明细+汇总级 | 原始数据 |
| Schema策略 | Schema-on-Write | Schema-on-Write | Schema-on-Read |
| 典型操作 | INSERT, UPDATE, DELETE | SELECT (复杂查询) | PUT, GET, 批处理 |
| 用户类型 | 业务操作人员 | 数据分析师/管理者 | 数据工程师/科学家 |
| 性能优化 | 高并发短事务 | 复杂查询吞吐量 | 存储成本与处理能力 |
技术架构对比图
┌─────────────────────────────────────────────────────────┐
│ 数据生态系统全景图 │
├─────────────────┬─────────────────┬─────────────────────┤
│ 业务数据库 │ 数据仓库 │ 数据湖 │
│ (OLTP系统) │ (OLAP系统) │ (原始数据池) │
├─────────────────┼─────────────────┼─────────────────────┤
│ MySQL │ Snowflake │ Amazon S3 │
│ PostgreSQL │ Redshift │ Azure Data Lake │
│ Oracle │ BigQuery │ HDFS │
│ │ Teradata │ │
├─────────────────┼─────────────────┼─────────────────────┤
│ ● 在线订单 │ ● 销售报表 │ ● 网站点击日志 │
│ ● 用户注册 │ ● 客户分群 │ ● 服务器监控数据 │
│ ● 库存更新 │ ● 趋势分析 │ ● IoT传感器数据 │
│ ● 支付处理 │ ● 预测模型 │ ● 社交媒体文本 │
├─────────────────┼─────────────────┼─────────────────────┤
│ 写入优化 │ 读取优化 │ 存储优化 │
│ 高并发 │ 复杂查询 │ 低成本 │
│ ACID事务 │ 批量加载 │ 高扩展性 │
└─────────────────┴─────────────────┴─────────────────────┘
│ │ │
↓ ↓ ↓
┌─────────────────────────────────────────────┐
│ 统一数据服务层 (Data API) │
├─────────────────────────────────────────────┤
│ ● 实时仪表盘 ● 自助分析 ● 数据科学平台 │
│ ● 移动报表 ● 预测预警 ● AI模型训练 │
└─────────────────────────────────────────────┘
查询模式对比示例
sql
-- 场景:分析2023年Q4各产品类别的销售情况
-- 1. 业务数据库查询(痛苦)
SELECT
c.category_name,
SUM(oi.quantity * oi.unit_price) as total_sales
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
LEFT JOIN customers cust ON o.customer_id = cust.customer_id
LEFT JOIN promotions prom ON o.promotion_id = prom.promotion_id
WHERE o.order_date BETWEEN '2023-10-01' AND '2023-12-31'
AND o.status IN ('COMPLETED', 'SHIPPED')
AND o.payment_status = 'PAID'
GROUP BY c.category_name
ORDER BY total_sales DESC;
-- 问题:8表关联,影响线上业务,查询耗时30秒+
-- 2. 数据仓库查询(高效)
SELECT
cat.category_name,
SUM(f.sales_amount) as total_sales
FROM fact_sales f
JOIN dim_date d ON f.date_key = d.date_key
JOIN dim_product p ON f.product_key = p.product_key
JOIN dim_category cat ON p.category_key = cat.category_key
WHERE d.year = 2023 AND d.quarter = 4
AND f.order_status = '已完成'
GROUP BY cat.category_name
ORDER BY total_sales DESC;
-- 优势:3表关联,预聚合,查询耗时<2秒
-- 3. 数据湖查询(原始分析)
-- 使用Spark SQL或Presto查询Parquet文件
spark.sql("""
SELECT
get_json_object(log, '$.category') as category,
COUNT(*) as event_count,
SUM(CAST(get_json_object(log, '$.amount') AS DOUBLE)) as total_amount
FROM raw_sales_logs
WHERE dt BETWEEN '2023-10-01' AND '2023-12-31'
AND get_json_object(log, '$.event_type') = 'purchase'
GROUP BY get_json_object(log, '$.category')
""")
-- 特点:处理半结构化数据,灵活性高
现代架构的融合趋势:湖仓一体
传统模式的问题:
数据湖 → (ETL) → 数据仓库 → BI工具
问题:数据移动成本高,存在数据冗余
湖仓一体架构:
┌─────────────────────┐
│ 统一元数据层 │
└──────────┬──────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 事务数据 │ │ 分析数据 │ │ 原始数据 │
│ (Delta) │ │ (Delta) │ │ (Parquet)│
└─────────┘ └─────────┘ └─────────┘
│ │ │
└──────────────┼──────────────┘
▼
┌──────────────┐
│ 统一查询引擎 │
│ (Spark/Trino)│
└──────────────┘
│
▼
┌──────────────┐
│ 多种工作负载 │
│ BI + AI + ...│
└──────────────┘
核心优势:
1. 单一副本:数据不移动,通过统一元数据管理
2. 多种工作负载:支持BI、数据科学、实时分析
3. 开放格式:使用Parquet、Delta、Iceberg等开放格式
4. ACID事务:保证数据一致性
本章小结与关键收获
-
数据仓库的核心价值:解决业务系统中的四个数据困境
- 分析性能问题
- 数据不一致问题
- 分析延迟问题
- 历史数据缺失问题
-
记住四个特性:面向主题、集成性、非易失性、时变性
- 这不仅是技术特性,更是业务价值的体现
- 每个特性都对应着特定的业务需求
-
理解三者的定位:
- 业务数据库:支持业务运转(记录系统)
- 数据仓库:支持业务决策(分析系统)
- 数据湖:存储数据资产(资源系统)
-
现代趋势:从分离走向融合,湖仓一体成为主流选择
思考题:
- 你所在的公司是否存在本章提到的数据困境?具体表现是什么?
- 如果要建立数据仓库,你认为应该优先解决哪个业务痛点?为什么?
- 结合你的业务场景,设计一个简单的主题域划分方案。
下一章预告: 我们将深入数据仓库的内部架构,了解经典的三层架构和现代技术栈选择。