TimescaleDB 時序資料庫完全指南:從 Hypertable 到連續聚合與壓縮策略 | PostgreSQL
TimescaleDB 是建構在 PostgreSQL 之上的時序資料庫擴展套件,讓你用熟悉的 SQL 語法處理大規模時間序列資料。從 Hypertable 自動分區、time_bucket 聚合函數到 Continuous Aggregates 即時物化與自動 壓縮 策略,本文帶你完整掌握 TimescaleDB 的核心功能與實戰技巧。
為什麼需要 TimescaleDB?
時間序列資料(Time-Series Data)無處不在——IoT 感測器每秒回傳溫濕度、應用程式每分鐘記錄回應時間、金融市場每毫秒產生價格變動。這類資料有幾個共同特徵:
| 特徵 | 說明 |
|---|---|
| 以時間為主軸 | 資料依時間戳排序,幾乎只有 INSERT,極少 UPDATE |
| 寫入量極大 | 每秒數千到數十萬筆 |
| 查詢以時間範圍為主 | 「最近 1 小時」「過去 7 天」是典型查詢模式 |
| 需要降取樣聚合 | 原始資料太多,需聚合成分鐘/小時/天粒度 |
| 舊資料逐漸降級 | 熱資料保留原始精度,冷資料壓縮或刪除 |
原生 PostgreSQL 可以存時序資料,但當資料量達到數億筆時,單一大表的索引維護、VACUUM 成本、查詢延遲都會急劇上升。TimescaleDB 解決了這些問題:
┌─────────────────────────────────────────────┐
│ TimescaleDB 架構 │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Hypertable(虛擬表) │ │
│ │ CREATE TABLE metrics (...) │ │
│ └────────────┬────────────────────┘ │
│ │ 自動分區 │
│ ┌────────┬───┴────┬────────┬────────┐ │
│ │Chunk 1 │Chunk 2 │Chunk 3 │Chunk 4 │ │
│ │ 1月 │ 2月 │ 3月 │ 4月 │ │
│ │(熱資料) │(溫資料) │(壓縮) │(壓縮) │ │
│ └────────┴────────┴────────┴────────┘ │
│ │
│ + Continuous Aggregates(即時物化視圖) │
│ + 自動壓縮(90%+ 壓縮比) │
│ + 資料保留策略(自動刪除過期資料) │
└─────────────────────────────────────────────┘
安裝與啟用
Ubuntu/Debian
# 加入 TimescaleDB APT 來源
sudo apt install -y gnupg postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
sudo sh -c "echo 'deb https://packagecloud.io/timescale/timescaledb/ubuntu/ $(lsb_release -cs) main' > /etc/apt/sources.list.d/timescaledb.list"
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg
sudo apt update
# 安裝(PostgreSQL 16 版本)
sudo apt install -y timescaledb-2-postgresql-16
# 執行調校精靈
sudo timescaledb-tune --quiet --yes
# 重啟 PostgreSQL
sudo systemctl restart postgresql
Docker
docker run -d --name timescaledb \
-p 5432:5432 \
-e POSTGRES_PASSWORD=password \
timescale/timescaledb:latest-pg16
啟用擴展
-- 在目標資料庫中啟用
CREATE EXTENSION IF NOT EXISTS timescaledb;
-- 確認版本
SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';
-- 2.17.2
Hypertable:自動分區的核心
Hypertable 是 TimescaleDB 的核心概念——它看起來像一張普通的 PostgreSQL 表,但底層會自動按時間維度切割成多個 Chunk(分區):
-- Step 1:建立普通表
CREATE TABLE sensor_data (
time TIMESTAMPTZ NOT NULL,
device_id TEXT NOT NULL,
temperature DOUBLE PRECISION,
humidity DOUBLE PRECISION,
battery DOUBLE PRECISION
);
-- Step 2:轉換為 Hypertable
-- chunk_time_interval 決定每個 Chunk 涵蓋的時間範圍
SELECT create_hypertable(
'sensor_data',
by_range('time', INTERVAL '1 day')
);
Chunk 時間間隔選擇
| 資料寫入速率 | 建議 chunk_time_interval | 理由 |
|---|---|---|
| < 1,000 筆/秒 | 1 week | 減少 Chunk 數量,降低管理成本 |
| 1,000-10,000 筆/秒 | 1 day | 平衡查詢效能與管理成本 |
| > 10,000 筆/秒 | 數小時 | 確保單一 Chunk 不會太大 |
多維度分區
除了時間維度,還可以加入空間維度(如 device_id)做 Hash 分區:
SELECT create_hypertable(
'sensor_data',
by_range('time', INTERVAL '1 day'),
by_hash('device_id', 4) -- 4 個 Hash 分區
);
查看 Chunk 資訊
-- 列出所有 Chunk
SELECT chunk_name, range_start, range_end,
pg_size_pretty(total_bytes) AS size
FROM timescaledb_information.chunks
WHERE hypertable_name = 'sensor_data'
ORDER BY range_start DESC
LIMIT 10;
time_bucket:時間聚合利器
time_bucket 是 TimescaleDB 最常用的函數,將時間戳對齊到指定的時間桶:
-- 每 5 分鐘平均溫度
SELECT
time_bucket('5 minutes', time) AS bucket,
device_id,
AVG(temperature) AS avg_temp,
MAX(temperature) AS max_temp,
MIN(temperature) AS min_temp,
COUNT(*) AS readings
FROM sensor_data
WHERE time > NOW() - INTERVAL '1 hour'
GROUP BY bucket, device_id
ORDER BY bucket DESC;
進階用法
-- 月度統計(使用 INTERVAL)
SELECT
time_bucket('1 month', time) AS month,
COUNT(*) AS total_readings,
AVG(temperature) AS avg_temp
FROM sensor_data
GROUP BY month
ORDER BY month;
-- 帶偏移量的時間桶(工廠三班制:08:00 開始)
SELECT
time_bucket('8 hours', time, '2026-01-01 08:00:00'::timestamptz) AS shift,
AVG(temperature) AS avg_temp
FROM sensor_data
GROUP BY shift
ORDER BY shift;
其他實用 Hyperfunctions
-- 近似行數(比 COUNT(*) 快 10-100 倍)
SELECT approximate_row_count('sensor_data');
-- 第一筆與最後一筆
SELECT
device_id,
first(temperature, time) AS first_temp,
last(temperature, time) AS last_temp
FROM sensor_data
GROUP BY device_id;
-- 時間加權平均(處理不規則取樣間隔)
SELECT
time_bucket('1 hour', time) AS bucket,
average(time_weight('linear', time, temperature)) AS tw_avg
FROM sensor_data
WHERE time > NOW() - INTERVAL '24 hours'
GROUP BY bucket
ORDER BY bucket;
Continuous Aggregates:即時物化視圖
當你反覆查詢相同的聚合(如「每小時平均溫度」),每次都掃描原始資料非常浪費。Continuous Aggregates(連續聚合)會自動將聚合結果物化,並在有新資料時增量更新:
-- 建立連續聚合:每小時設備統計
CREATE MATERIALIZED VIEW sensor_hourly
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS bucket,
device_id,
AVG(temperature) AS avg_temp,
MAX(temperature) AS max_temp,
MIN(temperature) AS min_temp,
AVG(humidity) AS avg_humidity,
COUNT(*) AS readings
FROM sensor_data
GROUP BY bucket, device_id
WITH NO DATA; -- 先不回填歷史資料
設定自動刷新策略
-- 自動刷新:每 30 分鐘刷新,範圍為 3 小時前到 1 小時前
SELECT add_continuous_aggregate_policy(
'sensor_hourly',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '30 minutes'
);
時間線:
3h前 1h前 現在
──────────────────────┤───────────┤──────────┤──→
│← 刷新範圍 →│
│ (物化) │ (即時) │
end_offset 的意義:最近 1 小時的資料可能還有延遲到達的寫入,暫不物化——查詢時 TimescaleDB 會自動合併物化結果與即時資料(Real-Time Aggregation)。
手動回填歷史
-- 回填過去 30 天
CALL refresh_continuous_aggregate(
'sensor_hourly',
NOW() - INTERVAL '30 days',
NOW() - INTERVAL '1 hour'
);
階層式聚合(Hierarchical)
可以在連續聚合之上再建連續聚合:
-- 日粒度(基於小時粒度的 sensor_hourly)
CREATE MATERIALIZED VIEW sensor_daily
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 day', bucket) AS day,
device_id,
AVG(avg_temp) AS avg_temp,
MAX(max_temp) AS max_temp,
MIN(min_temp) AS min_temp,
SUM(readings) AS total_readings
FROM sensor_hourly
GROUP BY day, device_id
WITH NO DATA;
SELECT add_continuous_aggregate_policy(
'sensor_daily',
start_offset => INTERVAL '3 days',
end_offset => INTERVAL '1 day',
schedule_interval => INTERVAL '1 hour'
);
壓縮:節省 90%+ 儲存空間
TimescaleDB 的 列式壓縮(Columnar Compression) 可以將冷資料壓縮 90-95%,同時保持 SQL 查詢能力:
-- 啟用壓縮
ALTER TABLE sensor_data SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id',
timescaledb.compress_orderby = 'time DESC'
);
參數說明
| 參數 | 用途 | 選擇指引 |
|---|---|---|
compress_segmentby | 壓縮段的分組欄位 | 選擇 WHERE 條件常用的低基數欄位(如 device_id) |
compress_orderby | 段內的排序方式 | 通常為 time DESC,加速 ORDER BY 查詢 |
自動壓縮策略
-- 超過 7 天的 Chunk 自動壓縮
SELECT add_compression_policy(
'sensor_data',
compress_after => INTERVAL '7 days'
);
查看壓縮效果
-- 壓縮統計
SELECT
pg_size_pretty(before_compression_total_bytes) AS before,
pg_size_pretty(after_compression_total_bytes) AS after,
ROUND(
(1 - after_compression_total_bytes::numeric /
before_compression_total_bytes::numeric) * 100, 1
) AS compression_ratio_pct
FROM hypertable_compression_stats('sensor_data');
典型結果:
before | after | compression_ratio_pct
---------+--------+-----------------------
12 GB | 980 MB | 91.8
手動壓縮特定 Chunk
-- 壓縮特定時間範圍
SELECT compress_chunk(c.chunk_name)
FROM timescaledb_information.chunks c
WHERE c.hypertable_name = 'sensor_data'
AND c.range_end < NOW() - INTERVAL '7 days'
AND NOT c.is_compressed;
資料保留策略
時序資料不需要永久保存——90 天前的原始秒級資料通常可以刪除(聚合結果保留在 Continuous Aggregate 中):
-- 自動刪除 90 天前的原始資料
SELECT add_retention_policy(
'sensor_data',
drop_after => INTERVAL '90 days'
);
-- 手動刪除(需要時)
SELECT drop_chunks(
'sensor_data',
older_than => INTERVAL '90 days'
);
完整的資料生命週期
原始資料 sensor_data
│
├── 0-7 天:未壓縮(熱資料,高速寫入/查詢)
├── 7-90 天:已壓縮(溫資料,壓縮 90%+)
└── 90 天後:自動刪除
↓
聚合資料 sensor_hourly
│
├── 0-180 天:小時粒度物化結果
└── 180 天後:自動刪除
↓
聚合資料 sensor_daily
│
└── 永久保留:天粒度物化結果
-- 為聚合也設定保留策略
SELECT add_retention_policy('sensor_hourly', drop_after => INTERVAL '180 days');
-- sensor_daily 永久保留,不設保留策略
實戰場景一:IoT 感測器監控
-- 完整的 IoT 資料模型
CREATE TABLE devices (
device_id TEXT PRIMARY KEY,
location TEXT,
device_type TEXT,
installed TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE sensor_readings (
time TIMESTAMPTZ NOT NULL,
device_id TEXT NOT NULL REFERENCES devices(device_id),
temperature DOUBLE PRECISION,
humidity DOUBLE PRECISION,
pressure DOUBLE PRECISION,
battery DOUBLE PRECISION
);
SELECT create_hypertable('sensor_readings', by_range('time', INTERVAL '1 day'));
-- 建立索引(Chunk 內的局部索引)
CREATE INDEX idx_readings_device_time
ON sensor_readings (device_id, time DESC);
-- 啟用壓縮
ALTER TABLE sensor_readings SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id',
timescaledb.compress_orderby = 'time DESC'
);
SELECT add_compression_policy('sensor_readings', compress_after => INTERVAL '7 days');
SELECT add_retention_policy('sensor_readings', drop_after => INTERVAL '90 days');
異常偵測查詢
-- 偵測溫度異常:超過該設備近 24 小時平均值 3 個標準差
WITH device_stats AS (
SELECT
device_id,
AVG(temperature) AS avg_temp,
STDDEV(temperature) AS std_temp
FROM sensor_readings
WHERE time > NOW() - INTERVAL '24 hours'
GROUP BY device_id
)
SELECT
r.time,
r.device_id,
r.temperature,
s.avg_temp,
ROUND((r.temperature - s.avg_temp) / NULLIF(s.std_temp, 0), 2) AS z_score
FROM sensor_readings r
JOIN device_stats s ON r.device_id = s.device_id
WHERE r.time > NOW() - INTERVAL '1 hour'
AND ABS(r.temperature - s.avg_temp) > 3 * s.std_temp
ORDER BY ABS(r.temperature - s.avg_temp) DESC;
設備電量預警
-- 電量低於 20% 且持續下降的設備
SELECT
device_id,
last(battery, time) AS current_battery,
first(battery, time) AS battery_1h_ago,
last(battery, time) - first(battery, time) AS battery_change
FROM sensor_readings
WHERE time > NOW() - INTERVAL '1 hour'
GROUP BY device_id
HAVING last(battery, time) < 20
AND last(battery, time) < first(battery, time)
ORDER BY current_battery ASC;
實戰場景二:APM 應用效能監控
-- APM 指標表
CREATE TABLE app_metrics (
time TIMESTAMPTZ NOT NULL,
service TEXT NOT NULL,
endpoint TEXT NOT NULL,
status_code INT,
response_ms DOUBLE PRECISION,
error BOOLEAN DEFAULT FALSE
);
SELECT create_hypertable('app_metrics', by_range('time', INTERVAL '1 day'));
-- 連續聚合:每分鐘的服務健康摘要
CREATE MATERIALIZED VIEW service_health_1m
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 minute', time) AS bucket,
service,
COUNT(*) AS total_requests,
COUNT(*) FILTER (WHERE error) AS error_count,
ROUND(
100.0 * COUNT(*) FILTER (WHERE error) / COUNT(*), 2
) AS error_rate,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY response_ms) AS p50,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY response_ms) AS p95,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY response_ms) AS p99
FROM app_metrics
GROUP BY bucket, service
WITH NO DATA;
SELECT add_continuous_aggregate_policy(
'service_health_1m',
start_offset => INTERVAL '10 minutes',
end_offset => INTERVAL '1 minute',
schedule_interval => INTERVAL '1 minute'
);
SLI/SLO 監控儀表板
-- 過去 1 小時的服務 SLI 摘要
SELECT
service,
SUM(total_requests) AS requests,
ROUND(AVG(error_rate), 2) AS avg_error_rate,
ROUND(AVG(p50), 1) AS avg_p50_ms,
ROUND(AVG(p95), 1) AS avg_p95_ms,
ROUND(AVG(p99), 1) AS avg_p99_ms,
CASE
WHEN AVG(error_rate) < 0.1 AND AVG(p99) < 500 THEN 'HEALTHY'
WHEN AVG(error_rate) < 1.0 AND AVG(p99) < 1000 THEN 'DEGRADED'
ELSE 'CRITICAL'
END AS status
FROM service_health_1m
WHERE bucket > NOW() - INTERVAL '1 hour'
GROUP BY service
ORDER BY avg_error_rate DESC;
實戰場景三:金融 OHLCV K 線
-- 逐筆交易表
CREATE TABLE trades (
time TIMESTAMPTZ NOT NULL,
symbol TEXT NOT NULL,
price DOUBLE PRECISION NOT NULL,
volume DOUBLE PRECISION NOT NULL
);
SELECT create_hypertable('trades', by_range('time', INTERVAL '1 day'));
-- 1 分鐘 K 線連續聚合
CREATE MATERIALIZED VIEW ohlcv_1m
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 minute', time) AS bucket,
symbol,
first(price, time) AS open,
MAX(price) AS high,
MIN(price) AS low,
last(price, time) AS close,
SUM(volume) AS volume
FROM trades
GROUP BY bucket, symbol
WITH NO DATA;
SELECT add_continuous_aggregate_policy(
'ohlcv_1m',
start_offset => INTERVAL '5 minutes',
end_offset => INTERVAL '1 minute',
schedule_interval => INTERVAL '1 minute'
);
-- 在 1 分鐘 K 線上建構 1 小時 K 線
CREATE MATERIALIZED VIEW ohlcv_1h
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', bucket) AS bucket,
symbol,
first(open, bucket) AS open,
MAX(high) AS high,
MIN(low) AS low,
last(close, bucket) AS close,
SUM(volume) AS volume
FROM ohlcv_1m
GROUP BY time_bucket('1 hour', bucket), symbol
WITH NO DATA;
SELECT add_continuous_aggregate_policy(
'ohlcv_1h',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '30 minutes'
);
移動平均線
-- 5 日與 20 日移動平均線
SELECT
bucket AS date,
symbol,
close,
AVG(close) OVER (
PARTITION BY symbol ORDER BY bucket
ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
) AS ma5,
AVG(close) OVER (
PARTITION BY symbol ORDER BY bucket
ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
) AS ma20
FROM ohlcv_1h
WHERE symbol = 'AAPL'
AND bucket > NOW() - INTERVAL '30 days'
ORDER BY bucket;
TimescaleDB vs 原生 PostgreSQL 分區
| 面向 | 原生 PARTITION | TimescaleDB Hypertable |
|---|---|---|
| 分區建立 | 手動 CREATE TABLE ... PARTITION OF | 自動按 chunk_time_interval 切割 |
| 分區數量管理 | 需手動建立未來分區 | 自動建立,無需維護 |
| 壓縮 | 無內建壓縮 | 列式壓縮 90%+ |
| 連續聚合 | 需自行實作 ETL pipeline | 內建 CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous) |
| 資料保留 | 手動 DROP TABLE partition_name | add_retention_policy 一行搞定 |
| 時間聚合函數 | date_trunc 有限粒度 | time_bucket 任意間隔 |
| 查詢最佳化 | 手動確保分區消除 | 自動 Chunk exclusion |
| 索引管理 | 每個分區各自管理 | 自動在新 Chunk 建立索引 |
Community vs Licensed 版本
| 功能 | Community(Apache 2) | Licensed(TSL) |
|---|---|---|
| Hypertable | ✅ | ✅ |
| time_bucket | ✅ | ✅ |
| 壓縮 | ✅ | ✅ |
| Continuous Aggregates | ✅ | ✅ |
| 資料保留策略 | ✅ | ✅ |
| 多節點分散式 | ❌ | ✅ |
| 列式掃描 Skip Scan | ❌ | ✅ |
| Tiered Storage(S3) | ❌ | ✅ |
大部分功能在 Community 版即可使用,只有多節點分散式和進階儲存分層需要 Licensed 版本。
效能調校要點
PostgreSQL 參數調整
-- 記憶體配置(以 64GB RAM 為例)
ALTER SYSTEM SET shared_buffers = '16GB';
ALTER SYSTEM SET effective_cache_size = '48GB';
ALTER SYSTEM SET work_mem = '256MB';
ALTER SYSTEM SET maintenance_work_mem = '2GB';
-- WAL 調整(高寫入場景)
ALTER SYSTEM SET wal_buffers = '64MB';
ALTER SYSTEM SET max_wal_size = '8GB';
ALTER SYSTEM SET checkpoint_completion_target = 0.9;
-- TimescaleDB 專用
ALTER SYSTEM SET timescaledb.max_background_workers = 8;
SELECT pg_reload_conf();
批次寫入最佳化
import psycopg2
from psycopg2.extras import execute_values
conn = psycopg2.connect("dbname=iot user=admin")
cur = conn.cursor()
# 使用 execute_values 批次插入(比逐筆 INSERT 快 10-50 倍)
data = [
('2026-07-21 10:00:00+08', 'sensor-001', 25.3, 60.1, 1013.2, 95.0),
('2026-07-21 10:00:01+08', 'sensor-002', 24.8, 62.5, 1013.1, 87.0),
# ... 數千筆
]
execute_values(
cur,
"""INSERT INTO sensor_readings
(time, device_id, temperature, humidity, pressure, battery)
VALUES %s""",
data,
page_size=1000 # 每批 1000 筆
)
conn.commit()
COPY 高速載入
# 從 CSV 高速載入(最快的方式)
timescaledb-parallel-copy \
--connection "host=localhost dbname=iot user=admin" \
--table sensor_readings \
--file sensor_data.csv \
--workers 4 \
--batch-size 10000
監控查詢
-- 各 Hypertable 的 Chunk 統計
SELECT
hypertable_name,
COUNT(*) AS num_chunks,
COUNT(*) FILTER (WHERE is_compressed) AS compressed_chunks,
pg_size_pretty(SUM(total_bytes)) AS total_size
FROM timescaledb_information.chunks
GROUP BY hypertable_name;
-- 背景排程任務狀態
SELECT *
FROM timescaledb_information.jobs
WHERE application_name LIKE 'User-Defined%'
OR proc_name IN ('policy_compression', 'policy_retention', 'policy_refresh_continuous_aggregate')
ORDER BY next_start;
-- 連續聚合刷新狀態
SELECT
view_name,
completed_threshold,
next_start
FROM timescaledb_information.continuous_aggregate_stats;
常見問題與解決方案
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 寫入變慢 | 單一連線瓶頸 | 使用連線池(PgBouncer)+ 批次寫入 |
| 查詢慢(大範圍) | 未使用 Continuous Aggregate | 建立適當粒度的連續聚合 |
| 磁碟空間暴增 | 未啟用壓縮 | add_compression_policy 並回壓歷史 Chunk |
| 壓縮後查詢慢 | segmentby 選擇不當 | 確保 WHERE 條件欄位在 compress_segmentby 中 |
| Chunk 過多 | chunk_time_interval 太短 | 調大間隔(不影響既有 Chunk) |
| 連續聚合延遲 | end_offset 太大 | 縮小 end_offset,接受輕微的 real-time 開銷 |
| UPDATE/DELETE 壓縮 Chunk | 壓縮 Chunk 不支援就地修改 | 先 decompress_chunk,修改後重新壓縮 |
總結
TimescaleDB 讓 PostgreSQL 成為一個高效能的時序資料庫,核心優勢在於:
- Hypertable 自動分區——不需手動管理分區表,按時間自動切割 Chunk
- time_bucket 聚合——任意時間粒度的降取樣,比
date_trunc更靈活 - Continuous Aggregates——增量更新的物化視圖,查詢速度提升 100-1000 倍
- 列式壓縮——冷資料壓縮 90%+,大幅節省儲存成本
- 資料保留策略——一行 SQL 搞定資料生命週期管理
- 完整 SQL 相容——所有 PostgreSQL 功能、工具、生態系都能直接使用
無論是 IoT 感測器、APM 應用監控還是金融數據分析,TimescaleDB 都是在 PostgreSQL 生態中處理時序資料的首選方案。
下一篇,我們將探討 資料庫遷移工具——從 Flyway、Liquibase 到 golang-migrate,掌握 Schema 版本控制與零停機遷移的實戰技巧。