超表(Hypertable) #

一、超表概述 #

1.1 什么是超表 #

超表(Hypertable)是TimescaleDB的核心概念,它是专门为时序数据设计的虚拟表,在底层自动将数据分区存储。

text
超表架构:

用户视角
│
├── Hypertable(超表)
│   └── 看起来像普通表
│       └── 使用标准SQL操作
│
底层实现
│
├── Chunk 1(分片1)
│   └── 时间范围:2024-01-01 ~ 2024-01-07
│
├── Chunk 2(分片2)
│   └── 时间范围:2024-01-08 ~ 2024-01-14
│
├── Chunk 3(分片3)
│   └── 时间范围:2024-01-15 ~ 2024-01-21
│
└── Chunk N(分片N)
    └── 时间范围:...

1.2 超表优势 #

text
超表优势:

性能优势
├── 分区裁剪 - 只扫描相关分区
├── 并行查询 - 多分片并行处理
├── 索引优化 - 每个分片独立索引
└── 写入优化 - 分片并行写入

管理优势
├── 自动分区 - 无需手动管理
├── 数据生命周期 - 自动清理旧数据
├── 压缩策略 - 自动压缩历史数据
└── 备份恢复 - 分片级别操作

兼容性优势
├── SQL兼容 - 标准SQL操作
├── 工具兼容 - PostgreSQL工具
├── 应用兼容 - 无需修改应用
└── 扩展兼容 - PostgreSQL扩展

1.3 超表 vs 普通表 #

text
对比普通表:

特性              普通表          超表
─────────────────────────────────────────
创建方式          CREATE TABLE    CREATE TABLE + create_hypertable
时间分区          手动            自动
空间分区          手动            自动
分区裁剪          需配置          自动
连续聚合          不支持          支持
压缩              不支持          支持
保留策略          手动            自动
SQL操作           标准            标准
索引              标准            标准

二、创建超表 #

2.1 基本创建 #

sql
-- 第一步:创建普通表
CREATE TABLE sensor_data (
    time        TIMESTAMPTZ NOT NULL,
    sensor_id   INTEGER NOT NULL,
    temperature DOUBLE PRECISION,
    humidity    DOUBLE PRECISION,
    pressure    DOUBLE PRECISION
);

-- 第二步:转换为超表
SELECT create_hypertable(
    'sensor_data',    -- 表名
    'time'            -- 时间列名
);

-- 查看超表信息
SELECT 
    hypertable_name,
    num_chunks,
    compression_state
FROM timescaledb_information.hypertables;

2.2 创建参数 #

sql
-- 完整参数创建
SELECT create_hypertable(
    'sensor_data',
    'time',
    partitioning_column => 'sensor_id',  -- 空间分区列
    number_partitions => 4,               -- 空间分区数
    chunk_time_interval => INTERVAL '7 days',  -- 时间分区间隔
    if_not_exists => TRUE,                -- 如果已存在不报错
    migrate_data => TRUE                  -- 迁移现有数据
);

2.3 时间分区间隔 #

sql
-- 设置时间分区间隔
SELECT set_chunk_time_interval('sensor_data', INTERVAL '1 day');

-- 或使用时间戳(微秒)
SELECT set_chunk_time_interval('sensor_data', 86400000000);

-- 常用分区间隔
-- 高频数据:1小时、6小时
-- 中频数据:1天、7天
-- 低频数据:1月、3月

-- 示例:不同场景的分区间隔
SELECT set_chunk_time_interval('high_freq_data', INTERVAL '1 hour');
SELECT set_chunk_time_interval('medium_freq_data', INTERVAL '1 day');
SELECT set_chunk_time_interval('low_freq_data', INTERVAL '1 month');

2.4 空间分区 #

sql
-- 创建带空间分区的超表
CREATE TABLE metrics (
    time        TIMESTAMPTZ NOT NULL,
    device_id   INTEGER NOT NULL,
    metric_name VARCHAR(100),
    value       DOUBLE PRECISION
);

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

-- 空间分区优势
-- 1. 提高并行写入性能
-- 2. 优化设备级别查询
-- 3. 分散I/O负载

三、超表管理 #

3.1 查看超表信息 #

sql
-- 查看所有超表
SELECT * FROM timescaledb_information.hypertables;

-- 查看超表详细信息
SELECT 
    hypertable_schema,
    hypertable_name,
    num_chunks,
    compression_state,
    is_distributed,
    replication_factor
FROM timescaledb_information.hypertables;

-- 查看超表维度信息
SELECT 
    hypertable_name,
    column_name,
    column_type,
    is_dimension
FROM timescaledb_information.dimensions;

-- 查看分片信息
SELECT 
    hypertable_name,
    chunk_name,
    range_start,
    range_end,
    is_compressed
FROM timescaledb_information.chunks
ORDER BY range_start DESC
LIMIT 10;

3.2 修改超表 #

sql
-- 修改时间分区间隔
SELECT set_chunk_time_interval('sensor_data', INTERVAL '14 days');

-- 添加空间分区(需要重建)
-- 注意:不能直接添加空间分区,需要重建超表

-- 修改表结构
ALTER TABLE sensor_data ADD COLUMN battery_level DOUBLE PRECISION;

-- 添加索引
CREATE INDEX idx_sensor_id ON sensor_data (sensor_id, time DESC);

-- 添加约束
ALTER TABLE sensor_data
ADD CONSTRAINT chk_temperature CHECK (temperature BETWEEN -50 AND 100);

3.3 删除超表 #

sql
-- 删除超表(保留数据)
DROP TABLE sensor_data;

-- 删除超表及其所有分片
SELECT drop_hypertable('sensor_data');

-- 级联删除
DROP TABLE sensor_data CASCADE;

四、超表操作 #

4.1 数据插入 #

sql
-- 插入单行
INSERT INTO sensor_data (time, sensor_id, temperature, humidity)
VALUES (NOW(), 1, 25.5, 60.0);

-- 批量插入
INSERT INTO sensor_data (time, sensor_id, temperature, humidity) VALUES
    (NOW() - INTERVAL '1 hour', 1, 24.8, 58.5),
    (NOW() - INTERVAL '2 hours', 1, 24.5, 57.2),
    (NOW() - INTERVAL '3 hours', 2, 26.1, 61.0);

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

4.2 数据查询 #

sql
-- 标准查询
SELECT * FROM sensor_data
WHERE time > NOW() - INTERVAL '24 hours'
ORDER BY time DESC;

-- 时间桶聚合
SELECT 
    time_bucket('1 hour', time) AS bucket,
    sensor_id,
    AVG(temperature) AS avg_temp,
    MAX(temperature) AS max_temp,
    MIN(temperature) AS min_temp
FROM sensor_data
WHERE time > NOW() - INTERVAL '24 hours'
GROUP BY bucket, sensor_id
ORDER BY bucket DESC;

-- 设备级别查询
SELECT * FROM sensor_data
WHERE sensor_id = 1
  AND time > NOW() - INTERVAL '7 days';

4.3 分区裁剪 #

sql
-- 分区裁剪示例
EXPLAIN ANALYZE
SELECT * FROM sensor_data
WHERE time > NOW() - INTERVAL '7 days';

-- 查看执行计划中的分片过滤
-- Chunk Filter: (time > NOW() - INTERVAL '7 days')
-- 只扫描相关分片,不扫描全表

-- 强制分区裁剪
SET timescaledb.enable_chunk_append = on;

-- 查看裁剪效果
SELECT 
    chunk_name,
    range_start,
    range_end
FROM timescaledb_information.chunks
WHERE range_end > NOW() - INTERVAL '7 days';

五、超表索引 #

5.1 创建索引 #

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

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

-- 复合索引
CREATE INDEX idx_sensor_time ON sensor_data (sensor_id, time DESC);

-- 条件索引
CREATE INDEX idx_high_temp ON sensor_data (time)
WHERE temperature > 30;

-- GIN索引(JSON列)
CREATE TABLE events (
    time TIMESTAMPTZ NOT NULL,
    data JSONB
);

SELECT create_hypertable('events', 'time');
CREATE INDEX idx_data_gin ON events USING GIN (data);

5.2 索引策略 #

text
索引策略建议:

时间列
├── 自动创建
├── 默认B-tree
└── 适合范围查询

设备ID列
├── 建议创建
├── 配合时间列
└── 适合设备查询

复合索引
├── (device_id, time DESC)
├── 覆盖常见查询
└── 注意顺序

特殊索引
├── 条件索引 - 特定条件
├── GIN索引 - JSON/数组
└── 部分索引 - 减少大小

六、超表最佳实践 #

6.1 分区间隔选择 #

text
分区间隔选择指南:

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

考虑因素
├── 数据量 - 每个分片建议1-50GB
├── 查询模式 - 时间范围查询
├── 保留策略 - 数据生命周期
└── 压缩策略 - 压缩时机

6.2 空间分区选择 #

text
空间分区选择指南:

适合空间分区
├── 多设备并发写入
├── 设备级别查询
├── 数据量大(TB级)
└── 高并发场景

不适合空间分区
├── 单一数据源
├── 全局聚合查询
├── 数据量小(GB级)
└── 低并发场景

分区数量建议
├── 2-4倍CPU核心数
├── 不超过设备数量
└── 根据写入并发调整

6.3 设计原则 #

sql
-- 好的超表设计
CREATE TABLE well_designed (
    time TIMESTAMPTZ NOT NULL,
    device_id INTEGER NOT NULL,
    metric_type SMALLINT,
    value DOUBLE PRECISION,
    tags JSONB
);

SELECT create_hypertable(
    'well_designed',
    'time',
    partitioning_column => 'device_id',
    number_partitions => 4,
    chunk_time_interval => INTERVAL '1 day'
);

-- 创建合适的索引
CREATE INDEX idx_device_time ON well_designed (device_id, time DESC);
CREATE INDEX idx_metric ON well_designed (metric_type, time DESC);

-- 设计原则
-- 1. 时间列使用TIMESTAMPTZ
-- 2. 标签列使用INTEGER或VARCHAR
-- 3. 数值列使用DOUBLE PRECISION
-- 4. 元数据使用JSONB
-- 5. 避免过多列

七、高级功能 #

7.1 自定义分区 #

sql
-- 创建自定义时间分区
SELECT create_hypertable(
    'sensor_data',
    'time',
    chunk_time_interval => INTERVAL '1 month'
);

-- 手动创建分片
SELECT create_chunk(
    'sensor_data',
    ARRAY['2024-01-01 00:00:00+00'::timestamptz, '2024-02-01 00:00:00+00'::timestamptz]
);

7.2 分片管理 #

sql
-- 查看分片大小
SELECT 
    hypertable_name,
    chunk_name,
    pg_size_pretty(total_bytes) as size
FROM chunk_relation_size('sensor_data')
ORDER BY total_bytes DESC;

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

-- 分割大分片
SELECT split_chunk('sensor_data_chunk_1');

7.3 超表复制 #

sql
-- 创建超表副本
CREATE TABLE sensor_data_copy (LIKE sensor_data INCLUDING ALL);

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

-- 复制数据
INSERT INTO sensor_data_copy
SELECT * FROM sensor_data;

八、监控与诊断 #

8.1 查看统计信息 #

sql
-- 超表统计
SELECT * FROM hypertable_detailed_size('sensor_data');

-- 分片统计
SELECT 
    hypertable_name,
    count(*) as chunk_count,
    sum(total_bytes) as total_size
FROM chunk_relation_size('sensor_data')
GROUP BY hypertable_name;

-- 查看压缩统计
SELECT 
    hypertable_name,
    chunk_name,
    before_compression_total_bytes,
    after_compression_total_bytes,
    100.0 * (before_compression_total_bytes - after_compression_total_bytes) / before_compression_total_bytes as compression_ratio
FROM chunk_compression_stats('sensor_data');

8.2 性能诊断 #

sql
-- 查看查询计划
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM sensor_data
WHERE time > NOW() - INTERVAL '7 days';

-- 查看索引使用
SELECT 
    schemaname,
    tablename,
    indexname,
    idx_scan,
    idx_tup_read,
    idx_tup_fetch
FROM pg_stat_user_indexes
WHERE tablename = 'sensor_data';

-- 查看缓存命中率
SELECT 
    sum(heap_blks_read) as heap_read,
    sum(heap_blks_hit) as heap_hit,
    sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as ratio
FROM pg_statio_user_tables
WHERE relname = 'sensor_data';

九、常见问题 #

9.1 创建超表失败 #

sql
-- 错误:表已有数据
-- 解决:使用migrate_data参数
SELECT create_hypertable(
    'sensor_data',
    'time',
    migrate_data => TRUE
);

-- 错误:时间列没有NOT NULL约束
-- 解决:添加约束
ALTER TABLE sensor_data ALTER COLUMN time SET NOT NULL;
SELECT create_hypertable('sensor_data', 'time');

9.2 分片过多 #

sql
-- 查看分片数量
SELECT count(*) FROM timescaledb_information.chunks
WHERE hypertable_name = 'sensor_data';

-- 解决:增大分区间隔
SELECT set_chunk_time_interval('sensor_data', INTERVAL '7 days');

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

9.3 查询性能差 #

sql
-- 检查索引
\d sensor_data

-- 添加合适的索引
CREATE INDEX idx_device_time ON sensor_data (device_id, time DESC);

-- 更新统计信息
ANALYZE sensor_data;

-- 检查执行计划
EXPLAIN ANALYZE SELECT ...;

十、总结 #

超表核心要点:

操作 命令 说明
创建 create_hypertable() 转换为超表
设置间隔 set_chunk_time_interval() 时间分区间隔
查看信息 timescaledb_information.hypertables 超表信息
查看分片 timescaledb_information.chunks 分片信息

最佳实践:

  1. 时间列:使用TIMESTAMPTZ,添加NOT NULL约束
  2. 分区间隔:根据数据量选择,每个分片1-50GB
  3. 空间分区:高并发场景使用,分区数为2-4倍CPU核心
  4. 索引策略:创建合适的复合索引
  5. 监控维护:定期检查分片数量和大小

下一步,让我们学习数据分片的详细知识!

最后更新:2026-03-27