时间分区 #

一、时间分区概述 #

1.1 什么是时间分区 #

时间分区是TimescaleDB将时序数据按时间维度自动分割存储的核心机制。

text
时间分区原理:

时间轴
─────────────────────────────────────────────►
│         │         │         │         │
│ Chunk 1 │ Chunk 2 │ Chunk 3 │ Chunk 4 │
│ 1月1-7日│ 1月8-14日│1月15-21日│1月22-28日│
│         │         │         │         │

每个分片存储固定时间范围的数据
├── 自动创建
├── 独立管理
└── 高效查询

1.2 时间分区优势 #

text
时间分区优势:

查询性能
├── 时间范围查询优化
├── 分区裁剪
├── 索引效率
└── 并行查询

写入性能
├── 热点分散
├── 索引维护优化
├── 并发写入
└── WAL优化

数据管理
├── 数据生命周期管理
├── 自动清理旧数据
├── 分级存储
└── 备份恢复优化

二、分区间隔设置 #

2.1 创建时设置 #

sql
-- 创建表
CREATE TABLE sensor_data (
    time TIMESTAMPTZ NOT NULL,
    sensor_id INTEGER,
    temperature DOUBLE PRECISION
);

-- 创建超表并设置分区间隔
SELECT create_hypertable(
    'sensor_data',
    'time',
    chunk_time_interval => INTERVAL '7 days'
);

2.2 修改分区间隔 #

sql
-- 查看当前分区间隔
SELECT 
    hypertable_name,
    chunk_interval
FROM timescaledb_information.dimensions;

-- 修改分区间隔
SELECT set_chunk_time_interval('sensor_data', INTERVAL '1 day');

-- 使用微秒设置
SELECT set_chunk_time_interval('sensor_data', 86400000000);  -- 1天

-- 修改只影响新创建的分片
-- 已有分片不受影响

2.3 分区间隔选择 #

text
分区间隔选择指南:

数据写入频率          推荐间隔
─────────────────────────────────
每秒多条              1-6小时
每分钟多条            6小时-1天
每小时多条            1-7天
每天多条              7-30天

数据量考虑
├── 每个分片1-50GB
├── 活跃分片不超过1000个
└── 考虑压缩和保留策略

查询模式考虑
├── 常用时间范围
├── 聚合时间粒度
└── 分区裁剪效率

2.4 不同场景示例 #

sql
-- 高频数据(每秒多条)
CREATE TABLE high_freq_data (
    time TIMESTAMPTZ NOT NULL,
    device_id INTEGER,
    value DOUBLE PRECISION
);

SELECT create_hypertable(
    'high_freq_data',
    'time',
    chunk_time_interval => INTERVAL '1 hour'
);

-- 中频数据(每分钟多条)
CREATE TABLE medium_freq_data (
    time TIMESTAMPTZ NOT NULL,
    device_id INTEGER,
    value DOUBLE PRECISION
);

SELECT create_hypertable(
    'medium_freq_data',
    'time',
    chunk_time_interval => INTERVAL '1 day'
);

-- 低频数据(每小时多条)
CREATE TABLE low_freq_data (
    time TIMESTAMPTZ NOT NULL,
    device_id INTEGER,
    value DOUBLE PRECISION
);

SELECT create_hypertable(
    'low_freq_data',
    'time',
    chunk_time_interval => INTERVAL '7 days'
);

三、分区维度 #

3.1 时间维度 #

sql
-- 单一时间维度分区
CREATE TABLE simple_data (
    time TIMESTAMPTZ NOT NULL,
    value DOUBLE PRECISION
);

SELECT create_hypertable('simple_data', 'time');

-- 时间维度是必需的
-- 所有超表必须有时间列

3.2 时间+空间维度 #

sql
-- 时间+空间双维度分区
CREATE TABLE multi_dim_data (
    time TIMESTAMPTZ NOT NULL,
    device_id INTEGER NOT NULL,
    value DOUBLE PRECISION
);

SELECT create_hypertable(
    'multi_dim_data',
    'time',
    partitioning_column => 'device_id',
    number_partitions => 4
);

-- 查看维度信息
SELECT 
    dimension_number,
    column_name,
    column_type,
    time_interval,
    num_slices
FROM timescaledb_information.dimensions
WHERE hypertable_name = 'multi_dim_data';

3.3 维度选择 #

text
维度选择指南:

单一时间维度
├── 适用场景
│   ├── 单一数据源
│   ├── 全局聚合查询
│   ├── 数据量小
│   └── 低并发场景
└── 优势
    ├── 管理简单
    ├── 查询优化
    └── 存储效率

时间+空间维度
├── 适用场景
│   ├── 多数据源
│   ├── 设备级别查询
│   ├── 大规模数据
│   └── 高并发场景
└── 优势
    ├── 并发写入
    ├── 查询并行
    └── I/O分散

四、分区裁剪 #

4.1 自动分区裁剪 #

sql
-- 时间范围查询(自动裁剪)
EXPLAIN ANALYZE
SELECT * FROM sensor_data
WHERE time > NOW() - INTERVAL '7 days';

-- 执行计划显示
-- -> Custom Scan (ChunkAppend)
--    -> Index Scan on _hyper_1_10_chunk  -- 只扫描相关分片
--    -> Index Scan on _hyper_1_11_chunk
--    -> Index Scan on _hyper_1_12_chunk

-- 查看扫描的分片
SELECT 
    chunk_name,
    range_start,
    range_end
FROM timescaledb_information.chunks
WHERE hypertable_name = 'sensor_data'
  AND range_end > NOW() - INTERVAL '7 days'
  AND range_start < NOW();

4.2 分区裁剪条件 #

sql
-- 有效的时间条件(触发裁剪)
SELECT * FROM sensor_data
WHERE time > '2024-01-01';

SELECT * FROM sensor_data
WHERE time BETWEEN '2024-01-01' AND '2024-01-31';

SELECT * FROM sensor_data
WHERE time >= '2024-01-01' AND time < '2024-02-01';

-- 无效的时间条件(不触发裁剪)
SELECT * FROM sensor_data
WHERE EXTRACT(MONTH FROM time) = 1;  -- 函数阻止裁剪

SELECT * FROM sensor_data
WHERE to_char(time, 'YYYY-MM') = '2024-01';  -- 函数阻止裁剪

4.3 优化分区裁剪 #

sql
-- 不推荐:使用函数
SELECT * FROM sensor_data
WHERE EXTRACT(YEAR FROM time) = 2024;

-- 推荐:使用范围
SELECT * FROM sensor_data
WHERE time >= '2024-01-01' AND time < '2025-01-01';

-- 不推荐:OR条件
SELECT * FROM sensor_data
WHERE time > '2024-01-01' OR sensor_id = 1;

-- 推荐:AND条件
SELECT * FROM sensor_data
WHERE time > '2024-01-01' AND sensor_id = 1;

-- 使用time_bucket优化
SELECT 
    time_bucket('1 day', time) AS day,
    AVG(value)
FROM sensor_data
WHERE time > NOW() - INTERVAL '30 days'
GROUP BY day;

五、分区管理 #

5.1 查看分区信息 #

sql
-- 查看所有分区
SELECT 
    hypertable_name,
    chunk_name,
    range_start,
    range_end,
    is_compressed
FROM timescaledb_information.chunks
WHERE hypertable_name = 'sensor_data'
ORDER BY range_start;

-- 查看分区间隔
SELECT 
    hypertable_name,
    column_name,
    time_interval
FROM timescaledb_information.dimensions;

-- 查看分区大小
SELECT 
    chunk_name,
    pg_size_pretty(total_bytes) as size,
    range_start,
    range_end
FROM chunk_relation_size('sensor_data') c
JOIN timescaledb_information.chunks ch
    ON c.chunk_name = ch.chunk_name
ORDER BY range_start DESC;

5.2 分区统计 #

sql
-- 按时间统计分区
SELECT 
    date_trunc('month', range_start) as month,
    count(*) as chunk_count,
    pg_size_pretty(sum(total_bytes)) as total_size
FROM chunk_relation_size('sensor_data') c
JOIN timescaledb_information.chunks ch
    ON c.chunk_name = ch.chunk_name
GROUP BY date_trunc('month', range_start)
ORDER BY month;

-- 分区大小分布
SELECT 
    CASE 
        WHEN total_bytes < 1073741824 THEN '< 1GB'
        WHEN total_bytes < 10737418240 THEN '1-10GB'
        WHEN total_bytes < 53687091200 THEN '10-50GB'
        ELSE '> 50GB'
    END as size_range,
    count(*) as chunk_count
FROM chunk_relation_size('sensor_data')
GROUP BY size_range
ORDER BY size_range;

5.3 分区调整 #

sql
-- 调整分区间隔
SELECT set_chunk_time_interval('sensor_data', INTERVAL '14 days');

-- 合并小分区
SELECT merge_chunks(
    'sensor_data',
    older_than => INTERVAL '30 days'
);

-- 分割大分区
SELECT split_chunk('_hyper_1_10_chunk');

六、分区与数据生命周期 #

6.1 数据保留策略 #

sql
-- 添加保留策略
SELECT add_retention_policy(
    'sensor_data',
    INTERVAL '90 days'
);

-- 查看保留策略
SELECT 
    job_id,
    schedule_interval,
    config
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention';

-- 手动删除旧分区
SELECT drop_chunks(
    'sensor_data',
    older_than => INTERVAL '90 days'
);

6.2 数据分层存储 #

sql
-- 热数据(最近7天)
-- 存储在SSD上,不压缩

-- 温数据(7-30天)
-- 压缩存储
SELECT add_compression_policy(
    'sensor_data',
    INTERVAL '7 days'
);

-- 冷数据(30-90天)
-- 压缩+低频访问
SELECT add_retention_policy(
    'sensor_data',
    INTERVAL '90 days'
);

6.3 分区与连续聚合 #

sql
-- 创建连续聚合
CREATE MATERIALIZED VIEW daily_stats
WITH (timescaledb.continuous) AS
SELECT 
    time_bucket('1 day', time) AS day,
    sensor_id,
    AVG(temperature) AS avg_temp,
    MAX(temperature) AS max_temp,
    MIN(temperature) AS min_temp
FROM sensor_data
GROUP BY day, sensor_id;

-- 连续聚合自动维护
-- 当原始分区更新时自动刷新

七、分区性能优化 #

7.1 索引优化 #

sql
-- 时间索引(自动创建)
-- create_hypertable自动创建

-- 设备ID索引
CREATE INDEX idx_sensor_id ON sensor_data (sensor_id, time DESC);

-- 复合索引
CREATE INDEX idx_sensor_time ON sensor_data (sensor_id, time DESC)
WHERE time > NOW() - INTERVAL '30 days';

-- 部分索引(减少大小)
CREATE INDEX idx_recent ON sensor_data (time DESC)
WHERE time > NOW() - INTERVAL '7 days';

7.2 查询优化 #

sql
-- 使用时间范围
SELECT * FROM sensor_data
WHERE time BETWEEN '2024-01-01' AND '2024-01-31';

-- 使用time_bucket
SELECT 
    time_bucket('1 hour', time) AS hour,
    AVG(temperature)
FROM sensor_data
WHERE time > NOW() - INTERVAL '24 hours'
GROUP BY hour;

-- 使用LIMIT
SELECT * FROM sensor_data
WHERE time > NOW() - INTERVAL '1 hour'
ORDER BY time DESC
LIMIT 100;

7.3 写入优化 #

sql
-- 批量插入
INSERT INTO sensor_data (time, sensor_id, temperature) VALUES
    (NOW(), 1, 25.5),
    (NOW(), 2, 26.0),
    (NOW(), 3, 24.8);

-- 使用COPY
COPY sensor_data FROM '/path/to/data.csv' WITH (FORMAT csv, HEADER true);

-- 临时关闭自动压缩(大批量导入)
SELECT remove_compression_policy('sensor_data');
-- 导入数据
-- 重新添加压缩策略
SELECT add_compression_policy('sensor_data', INTERVAL '7 days');

八、分区监控 #

8.1 分区健康检查 #

sql
-- 检查分区大小分布
SELECT 
    hypertable_name,
    count(*) as chunk_count,
    pg_size_pretty(avg(total_bytes)::bigint) as avg_size,
    pg_size_pretty(min(total_bytes)::bigint) as min_size,
    pg_size_pretty(max(total_bytes)::bigint) as max_size
FROM chunk_relation_size('sensor_data')
GROUP BY hypertable_name;

-- 检查分区裁剪效率
EXPLAIN ANALYZE
SELECT * FROM sensor_data
WHERE time > NOW() - INTERVAL '7 days';

-- 检查压缩比例
SELECT 
    count(*) as total,
    sum(CASE WHEN is_compressed THEN 1 ELSE 0 END) as compressed,
    round(100.0 * sum(CASE WHEN is_compressed THEN 1 ELSE 0 END) / count(*), 2) as rate
FROM timescaledb_information.chunks
WHERE hypertable_name = 'sensor_data';

8.2 分区告警 #

sql
-- 分片数量告警
SELECT 
    hypertable_name,
    count(*) as chunk_count,
    CASE 
        WHEN count(*) > 1000 THEN 'WARNING: Too many chunks'
        WHEN count(*) > 500 THEN 'CAUTION: Many chunks'
        ELSE 'OK'
    END as status
FROM timescaledb_information.chunks
GROUP BY hypertable_name;

-- 分片大小告警
SELECT 
    chunk_name,
    pg_size_pretty(total_bytes) as size,
    CASE 
        WHEN total_bytes > 53687091200 THEN 'WARNING: Chunk too large'
        WHEN total_bytes < 1073741824 THEN 'WARNING: Chunk too small'
        ELSE 'OK'
    END as status
FROM chunk_relation_size('sensor_data')
WHERE total_bytes > 53687091200 OR total_bytes < 1073741824;

九、分区最佳实践 #

9.1 设计原则 #

text
分区设计原则:

时间列选择
├── 使用TIMESTAMPTZ
├── 添加NOT NULL约束
├── 考虑时区问题
└── 选择合适的时间精度

分区间隔选择
├── 根据数据量
├── 根据查询模式
├── 考虑保留策略
└── 考虑压缩策略

空间分区选择
├── 根据并发需求
├── 根据查询模式
├── 考虑设备数量
└── 考虑写入频率

9.2 常见问题解决 #

sql
-- 问题1:分片过多
-- 解决:增大分区间隔
SELECT set_chunk_time_interval('sensor_data', INTERVAL '14 days');

-- 问题2:分片大小不均
-- 解决:调整分区间隔或合并分片
SELECT merge_chunks('sensor_data', older_than => INTERVAL '30 days');

-- 问题3:查询性能差
-- 解决:确保时间条件、添加索引
CREATE INDEX idx_time ON sensor_data (time DESC);
ANALYZE sensor_data;

十、总结 #

时间分区要点:

操作 命令 说明
设置间隔 set_chunk_time_interval() 设置分区间隔
查看分区 timescaledb_information.chunks 查看分区信息
合并分区 merge_chunks() 合并小分区
删除分区 drop_chunks() 删除旧数据

最佳实践:

  1. 分区间隔:根据数据量选择,每个分片1-50GB
  2. 时间列:使用TIMESTAMPTZ,添加NOT NULL约束
  3. 分区裁剪:确保查询使用时间条件
  4. 索引策略:创建合适的复合索引
  5. 监控维护:定期检查分区状态

下一步,让我们学习数据插入操作!

最后更新:2026-03-27