4、数据库调优(PostgreSQL):连接池配置(HikariCP)、索引优化、慢查询分析与VACUUM策略

数据库调优,说白了就是让 PostgreSQL 别成为 ThingsBoard 的瓶颈。我见过太多团队,微服务拆得飞起,结果数据库一压就垮。今天咱们就聊聊连接池、索引、慢查询和 VACUUM 这四个核心点。

4.1 连接池配置(HikariCP)

ThingsBoard 默认用的是 HikariCP,这玩意儿号称「史上最快连接池」。我个人习惯是,先别急着改参数,搞清楚你的业务场景再说。

核心参数配置

参数名 推荐值 说明
maximumPoolSize CPU核心数 × 2 + 1 别贪多,连接数不是越多越好
minimumIdle 10-20 保持一些空闲连接,应对突发
connectionTimeout 30000ms 30秒拿不到连接就报错
idleTimeout 600000ms 10分钟空闲就回收
maxLifetime 1800000ms 30分钟强制重建连接

避坑指南

我曾经遇到一个客户,把 maximumPoolSize 设成了 200,结果 PostgreSQL 直接 OOM。你想想看,每个连接都要占用内存,200个连接光缓冲区就吃掉好几个 GB。后来我建议改成 50,性能反而上去了。

ThingsBoard 中的配置方式

在 thingsboard.yml 里找到这一段:

spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 20
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

嗯,这里要注意:如果你的 ThingsBoard 是集群部署,每个节点都要独立配置连接池。别想着共享,那会出大问题。

4.2 索引优化

索引这东西,用好了是神器,用不好就是累赘。我见过有人给每个字段都加索引,结果写入性能惨不忍睹。

ThingsBoard 核心表索引建议

表名 推荐索引 原因
ts_kv (entity_id, key, ts) 时序数据查询最频繁
device (tenant_id, type) 按租户和设备类型过滤
alarm (tenant_id, status, created_time) 告警查询常用条件
event (tenant_id, event_type, created_time) 事件日志查询

我的经验

ts_kv 表是 ThingsBoard 的「心脏」,数据量最大。我建议用复合索引,把 entity_id 放第一位。为什么?因为查询时先定位设备,再找 key,最后按时间排序,这样索引利用率最高。

创建索引的 SQL 示例

-- 时序数据核心索引
CREATE INDEX idx_ts_kv_entity_key_ts 
ON ts_kv (entity_id, key, ts DESC);

-- 告警表常用索引
CREATE INDEX idx_alarm_tenant_status_time 
ON alarm (tenant_id, status, created_time DESC);

-- 记得定期检查未使用的索引
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0;

这个查询能帮你找出「僵尸索引」——那些建了但从来没用过的索引。我建议每个月跑一次,该删就删。

4.3 慢查询分析

慢查询是性能问题的「照妖镜」。我个人习惯是,先开启慢查询日志,再分析。

开启慢查询日志

-- postgresql.conf 中配置
log_min_duration_statement = 1000  -- 超过1秒的查询记录
log_duration = on
log_statement = 'ddl'  -- 只记录DDL语句,避免日志爆炸

配置完记得重启 PostgreSQL。然后你就可以用 pgBadger 或者自带的 pg_stat_statements 来分析。

常用分析查询

-- 查看最耗时的查询
SELECT query, calls, total_time, mean_time, rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

-- 查看执行频率最高的查询
SELECT query, calls, rows, 
       rows/calls AS avg_rows
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 10;

注意

pg_stat_statements 需要安装扩展:CREATE EXTENSION pg_stat_statements。我遇到过有人没装这个扩展,折腾半天查不到数据。

我曾经接手一个项目,ThingsBoard 的告警页面加载要 8 秒。一查慢查询日志,发现有个查询没走索引,全表扫描了 500 万条记录。加了个索引后,直接降到 200 毫秒。你想想看,一个索引能差 40 倍。

4.4 VACUUM 策略

PostgreSQL 的 MVCC 机制,说白了就是「旧数据不删,新数据追加」。时间长了,表里全是死元组,查询性能直线下降。VACUUM 就是干这个的——清理死元组,回收空间。

自动 VACUUM 配置

# postgresql.conf
autovacuum = on
autovacuum_naptime = 1min
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.1

手动 VACUUM 策略

操作 命令 适用场景
普通清理 VACUUM; 日常维护,不锁表
完全清理 VACUUM FULL; 空间回收,会锁表,建议凌晨执行
分析统计 ANALYZE; 更新查询计划,建议每天一次
组合操作 VACUUM ANALYZE; 清理+分析,最常用

避坑指南

我曾经在白天执行了 VACUUM FULL,结果整个 ThingsBoard 停了 15 分钟。为什么?VACUUM FULL 会锁表,所有读写操作都得排队。后来我改成凌晨 3 点执行,配合 cron 定时任务,再也没出过问题。

监控 VACUUM 状态

-- 查看表的死元组比例
SELECT relname, n_dead_tup, n_live_tup,
       round(n_dead_tup * 100.0 / (n_live_tup + n_dead_tup), 2) AS dead_pct
FROM pg_stat_user_tables
WHERE n_live_tup > 0
ORDER BY dead_pct DESC
LIMIT 10;

当死元组比例超过 20% 时,我建议手动执行一次 VACUUM。超过 50% 就得考虑 VACUUM FULL 了。

总结

数据库调优不是一锤子买卖。我建议你按这个顺序来:

  1. 先配好连接池,别让连接数成为瓶颈
  2. 再优化索引,重点照顾 ts_kv 表
  3. 开启慢查询日志,持续监控
  4. 制定 VACUUM 策略,定期清理

嗯,做到这四点,你的 ThingsBoard 数据库基本就稳了。剩下的就是根据业务量持续调整参数。记住,没有一劳永逸的配置,只有持续优化的过程。