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