Spanner数据分区 #

一、分区概述 #

1.1 分区概念 #

text
Spanner分区类型
├── 自动分区
│   ├── 按主键范围自动分片
│   ├── 自动负载均衡
│   ├── 透明无感知
│   └── 默认行为
│
└── 表分区
    ├── 按时间或范围分区
    ├── 分区裁剪优化
    ├── 分区过期策略
    └── 需要显式定义

1.2 分区优势 #

text
表分区优势:
├── 分区裁剪: 减少扫描数据量
├── 分区过期: 自动删除旧数据
├── 查询优化: 提高查询性能
├── 管理简化: 按分区管理数据
└── 成本控制: 减少存储和计算

二、创建分区表 #

2.1 按时间分区 #

sql
-- 按日期分区
CREATE TABLE logs (
    log_id INT64 NOT NULL,
    log_date DATE NOT NULL,
    message STRING(MAX),
    level STRING(10)
) PRIMARY KEY (log_id, log_date)
PARTITION BY DATE(log_date);

-- 按时间戳分区
CREATE TABLE events (
    event_id INT64 NOT NULL,
    event_time TIMESTAMP NOT NULL,
    data STRING(MAX)
) PRIMARY KEY (event_id, event_time)
PARTITION BY DATE(event_time);

2.2 分区键要求 #

text
分区键要求:
├── 必须是主键的一部分
├── 通常是主键最后一列
├── 类型必须是DATE或TIMESTAMP
├── 不能修改分区键值
└── 不能为NULL

2.3 分区选项 #

sql
-- 创建带过期时间的分区表
CREATE TABLE logs (
    log_id INT64 NOT NULL,
    log_date DATE NOT NULL,
    message STRING(MAX)
) PRIMARY KEY (log_id, log_date)
PARTITION BY DATE(log_date)
OPTIONS (
    partition_expiration_days = 30  -- 30天后自动删除
);

三、分区查询 #

3.1 分区裁剪 #

sql
-- 分区裁剪: 只扫描需要的分区
SELECT * FROM logs
WHERE log_date = DATE '2024-03-27';

-- 范围查询
SELECT * FROM logs
WHERE log_date BETWEEN DATE '2024-03-01' AND DATE '2024-03-31';

-- 不推荐: 函数导致全分区扫描
SELECT * FROM logs
WHERE DATE(log_date) = DATE '2024-03-27';  -- 不使用分区裁剪

-- 推荐: 直接使用分区列
SELECT * FROM logs
WHERE log_date = DATE '2024-03-27';  -- 使用分区裁剪

3.2 分区查询优化 #

sql
-- 使用分区列作为条件
SELECT * FROM logs
WHERE log_date >= DATE '2024-03-01'
  AND log_date < DATE '2024-04-01'
  AND level = 'ERROR';

-- 避免在分区列上使用函数
-- 不推荐
SELECT * FROM logs
WHERE EXTRACT(MONTH FROM log_date) = 3;

-- 推荐
SELECT * FROM logs
WHERE log_date >= DATE '2024-03-01'
  AND log_date < DATE '2024-04-01';

3.3 分区统计 #

sql
-- 查看分区统计
SELECT 
    log_date,
    COUNT(*) AS count,
    SUM(BYTE_LENGTH(message)) AS size
FROM logs
GROUP BY log_date
ORDER BY log_date;

四、分区管理 #

4.1 分区过期 #

sql
-- 设置分区过期时间
ALTER TABLE logs SET OPTIONS (
    partition_expiration_days = 30
);

-- 查看分区过期设置
SELECT 
    table_name,
    option_name,
    option_value
FROM INFORMATION_SCHEMA.TABLE_OPTIONS
WHERE table_name = 'logs';

4.2 手动删除分区 #

sql
-- Spanner不支持直接删除分区
-- 使用DELETE删除数据
DELETE FROM logs
WHERE log_date < DATE '2024-01-01';

-- 分批删除大量数据
DELETE FROM logs
WHERE log_date < DATE '2024-01-01'
LIMIT 10000;

4.3 分区信息查询 #

sql
-- 查看分区表信息
SELECT 
    table_name,
    partition_expression
FROM INFORMATION_SCHEMA.TABLES
WHERE table_name = 'logs';

-- 查看分区列
SELECT 
    column_name,
    data_type
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'logs'
  AND column_name = 'log_date';

五、分区设计 #

5.1 分区键选择 #

text
分区键选择原则:
├── 选择查询常用的过滤条件
├── 通常是时间列
├── 数据分布均匀
├── 避免热点分区
└── 考虑数据保留策略

5.2 主键设计 #

sql
-- 分区键必须是主键的一部分
-- 通常放在主键最后

-- 推荐
CREATE TABLE logs (
    log_id INT64 NOT NULL,
    log_date DATE NOT NULL,
    ...
) PRIMARY KEY (log_id, log_date)  -- 分区键在最后
PARTITION BY DATE(log_date);

-- 不推荐(分区键不在主键中)
CREATE TABLE logs_bad (
    log_id INT64 NOT NULL,
    log_date DATE NOT NULL,
    ...
) PRIMARY KEY (log_id)  -- 缺少分区键
PARTITION BY DATE(log_date);  -- ERROR

5.3 分区粒度 #

text
分区粒度选择:
├── 日分区: 适合日志、事件数据
├── 月分区: 适合中等数据量
├── 年分区: 适合历史数据
└── 根据数据量和查询模式选择

六、分区与交错表 #

6.1 分区交错表 #

sql
-- 父表分区
CREATE TABLE users (
    user_id INT64 NOT NULL,
    created_date DATE NOT NULL,
    name STRING(100)
) PRIMARY KEY (user_id, created_date)
PARTITION BY DATE(created_date);

-- 子表继承分区
CREATE TABLE orders (
    user_id INT64 NOT NULL,
    created_date DATE NOT NULL,
    order_id INT64 NOT NULL,
    amount FLOAT64
) PRIMARY KEY (user_id, created_date, order_id)
PARTITION BY DATE(created_date)
INTERLEAVE IN PARENT users ON DELETE CASCADE;

6.2 分区交错表查询 #

sql
-- 查询特定日期的用户和订单
SELECT u.name, o.order_id, o.amount
FROM users u
INNER JOIN orders o 
    ON u.user_id = o.user_id 
    AND u.created_date = o.created_date
WHERE u.created_date = DATE '2024-03-27';

七、分区性能 #

7.1 分区裁剪效果 #

sql
-- 查看执行计划
EXPLAIN SELECT * FROM logs
WHERE log_date = DATE '2024-03-27';

-- 分区裁剪指标
-- partitions_scanned: 扫描的分区数
-- partitions_total: 总分区数

7.2 性能对比 #

sql
-- 不使用分区裁剪(全表扫描)
SELECT * FROM logs WHERE message LIKE '%error%';

-- 使用分区裁剪(分区扫描)
SELECT * FROM logs 
WHERE log_date = DATE '2024-03-27'
  AND message LIKE '%error%';

7.3 性能优化建议 #

text
分区性能优化:
├── 使用分区裁剪
├── 避免全分区扫描
├── 合理设置分区粒度
├── 创建合适的索引
└── 监控分区大小

八、分区最佳实践 #

8.1 使用场景 #

text
适合分区表的场景:
├── 时序数据(日志、事件)
├── 需要定期删除旧数据
├── 按时间范围查询
├── 数据量大且有时间属性
└── 需要分区级别管理

8.2 设计建议 #

text
分区设计建议:
├── 选择合适的分区键
├── 分区键放在主键最后
├── 设置合理的过期时间
├── 使用分区裁剪优化查询
└── 监控分区大小和性能

8.3 注意事项 #

text
分区注意事项:
├── 分区键不能修改
├── 分区键不能为NULL
├── 分区过期后数据不可恢复
├── 避免分区热点
└── 合理规划分区数量

九、总结 #

分区表优势:

优势 说明
分区裁剪 减少扫描数据量
分区过期 自动删除旧数据
查询优化 提高查询性能
管理简化 按分区管理数据

最佳实践:

text
1. 选择合适的分区键
   └── 通常是时间列

2. 使用分区裁剪
   └── 查询时指定分区条件

3. 设置分区过期
   └── 自动清理旧数据

4. 监控分区性能
   └── 及时发现热点

5. 合理设计主键
   └── 分区键放在最后

下一步,让我们学习TrueTime机制!

最后更新:2026-03-27