Featured image of post PostgreSQL 实战:配置调优、扩展生态与 MySQL 迁移

PostgreSQL 实战:配置调优、扩展生态与 MySQL 迁移

本文整理 PostgreSQL 日常运维的关键场景:最大连接数与 pg_stat_statements 扩展、批量加主键、Truncate 多表;从 MySQL 8 迁移到 PostgreSQL 17 的多租户改造与 TimescaleDB 时序插件集成。

为什么写这篇:PostgreSQL 是最像 Oracle 的开源数据库——扩展生态强、SQL 严谨、ACID 完整。本文覆盖三件事:①配置调优(连接数、扩展);②日常 SQL(批量加主键、Truncate);③从 MySQL 8 迁移到 PostgreSQL 17 的多租户改造。

适用读者:需要在生产环境用 PG、要做 MySQL→PG 迁移的 DBA / 后端。

前置知识:会用 psql 客户端、懂 SQL、知道什么是 schema。

目录

  1. 连接数与运行时配置
  2. pg_stat_statements:SQL 性能分析扩展
  3. 批量加主键:生成 ALTER TABLE 脚本
  4. 多表 Truncate 与多模式管理
  5. 从 MySQL 8 迁移到 PostgreSQL 17
  6. 多租户改造:MySQL → PG + TimescaleDB

1. 连接数与运行时配置

1.1 查看配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 显示最大连接数
SHOW max_connections;

-- 查总连接数
SELECT count(*) FROM pg_stat_activity;

-- 加载配置(部分参数能热加载)
SELECT pg_reload_conf();

-- 查看配置文件地址
SHOW config_file;

1.2 在线调整连接数

1
2
3
4
5
6
7
8
9
-- 在线调(部分参数)
-- 注意:max_connections 不能 SELECT pg_reload_conf() 生效,必须重启

-- 持久化
vim /var/lib/postgresql/data/postgresql.conf
# max_connections = 10000   # 默认 100 改成 10000

# 重启
docker restart timescaledb

1.3 客户端连接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
psql -U postgres
# 默认连 postgres 库

# 切到指定库
\c videohub
# You are now connected to database "videohub" as user "postgres".

# 查所有库
\l+

# 查当前库所有表
\dt

1.4 列出所有数据库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
postgres=# \l+
                                                                       List of databases
       Name        |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges   |   Size   | Tablespace |                Description
-------------------+----------+----------+------------+------------+-----------------------+---------+------------+--------------------------------------------
 plsdb             | postgres | UTF8     | en_US.utf8 | en_US.utf8 |                       | 8401 kB  | pg_default |
 postgis_25_sample | postgres | UTF8     | en_US.utf8 | en_US.utf8 |                       | 11 MB    | pg_default |
 postgres          | postgres | UTF8     | en_US.utf8 | en_US.utf8 |                       | 8553 kB  | pg_default | default administrative connection database
 template0         | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +| 8401 kB  | pg_default | unmodifiable empty database
 template1         | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +| 8401 kB  | pg_default | unmodifiable empty database
 videohub          | postgres | UTF8     | en_US.utf8 | en_US.utf8 |                       | 10 MB    | pg_default |

2. pg_stat_statements:SQL 性能分析扩展

pg_stat_statements 是 PG 自带的 SQL 慢查询分析扩展,类似 MySQL 的 performance_schema.events_statements_summary_by_digest

2.1 启用

1
2
3
4
5
6
-- 1. 创建扩展(一次)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 2. 验证
SELECT * FROM pg_extension WHERE extname = 'pg_stat_statements';
SELECT extversion FROM pg_extension WHERE extname = 'pg_stat_statements';

2.2 配置

1
2
3
4
5
6
7
8
# /var/lib/postgresql/data/postgresql.conf

# 768 行:把 pg_stat_statements 加入 shared_preload_libraries
shared_preload_libraries = 'timescaledb,pg_stat_statements'

# 642 行:默认 none,建议 all
track_functions = all
pg_stat_statements.track = all

修改后重启 PG

2.3 查询 Top 50 慢 SQL

1
2
3
4
5
6
7
8
9
SELECT
    query,
    calls,
    total_time,
    rows,
    100.0 * shared_blks_hit / nullif(shared_blks_total, 0) AS hit_percent
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 50;
字段含义
querySQL 模板(参数化)
calls执行次数
total_time总耗时(ms)
rows总返回行数
hit_percentshared buffer 命中率(目标 ≥ 99%

3. 批量加主键:生成 ALTER TABLE 脚本

3.1 查某个模式下所有"无主键"的表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
SELECT
    n.nspname AS schema_name,
    c.relname AS table_name,
    COALESCE(d.description, '无注释') AS table_comment
FROM
    pg_class c
    JOIN pg_namespace n ON c.relnamespace = n.oid
    LEFT JOIN pg_description d
        ON c.oid = d.objoid AND d.objsubid = 0
WHERE
    c.relkind = 'r'  -- 普通用户表
    AND n.nspname = 'mine_dev'  -- 替换为实际 schema
    AND NOT EXISTS (
        SELECT 1 FROM pg_constraint con
        WHERE con.conrelid = c.oid AND con.contype = 'p'  -- p = PRIMARY KEY
    )
ORDER BY c.relname;

3.2 批量生成"加主键" ALTER 脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT
    'ALTER TABLE "' || n.nspname || '"."' || c.relname || '" ADD PRIMARY KEY ("id");'
    AS add_pk_sql
FROM
    pg_class c
    JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE
    c.relkind = 'r'
    AND n.nspname = 'mine_dev'
    AND NOT EXISTS (
        SELECT 1 FROM pg_constraint con
        WHERE con.conrelid = c.oid AND con.contype = 'p'
    )
    AND EXISTS (
        -- 确保表有 id 字段
        SELECT 1 FROM pg_attribute att
        WHERE att.attrelid = c.oid
          AND att.attname = 'id'
          AND att.attnum > 0
          AND NOT att.attisdropped
    )
ORDER BY n.nspname, c.relname;

输出

1
2
3
4
5
add_pk_sql
-----------------------------------------------------------------
ALTER TABLE "mine_dev"."dps_risk_task" ADD PRIMARY KEY ("id");
ALTER TABLE "mine_dev"."dps_risk_object" ADD PRIMARY KEY ("id");
...

把结果复制出来逐条执行(不要全选执行——某些表加主键会因重复值失败)。


4. 多表 Truncate 与多模式管理

4.1 一行清空多张表

1
TRUNCATE TABLE table1, table2, table3;

4.2 模式(Schema)实战

PG 的 schema 跟 MySQL 的"库"概念类似但不同——一个数据库可以有多个 schema:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 查所有 schema
SELECT nspname FROM pg_namespace;

-- 跨 schema 查询
SELECT * FROM mine_dev.risk_task
UNION ALL
SELECT * FROM other_dev.risk_task;

-- 用 schema 隔离多租户
CREATE SCHEMA tenant_company_a;
CREATE SCHEMA tenant_company_b;

5. 从 MySQL 8 迁移到 PostgreSQL 17

5.1 工具选型

工具适用备注
Navicat Premium 数据传输库到库迁移图形化,但要手动建库
pgloaderMySQL → PG 一键迁移命令行,推荐
mysql_fdw实时跨库查询PG 端当外部表
DMS / DTS云服务阿里云、AWS 都提供

5.2 Navicat Premium 实战

场景internal.example.com:30500 的 MySQL 8 → internal.example.com:5432 的 PostgreSQL 17。

1
2
原库:industry_iot_dev   postgres/****
目标库:internal.example.com:5432/postgres/mine_dev

迁移过程中常见错误

1
2
3
4
5
6
7
8
9
[DTF] 106> dps_risk_task_record_history: Create index "uk_task_dup"
[ERR] 343> CREATE UNIQUE INDEX "uk_task_dup" ON "mine_dev"."dps_risk_task_record_history" USING btree (
  "control_id",
  "task_begin_date",
  "task_end_date"
)
[ERR] 343> ERROR:  relation "uk_task_dup" already exists

[DTF] Process terminated

原因:源库某表的索引名 uk_task_dup 在目标库已存在(可能之前的迁移残留)。修法:改一下索引名。

5.3 Navicat 迁移后必须手改的差异点

维度MySQLPostgreSQL改法
主键自增AUTO_INCREMENTSERIALGENERATED BY DEFAULT AS IDENTITY重写 DDL
时间戳默认值CURRENT_TIMESTAMPCURRENT_TIMESTAMP兼容
字符集utf8mb4数据库级设置统一
关键字引号`"全局替换
LIMIT 分页LIMIT offset, sizeLIMIT size OFFSET offset顺序反
IF NOT EXISTS表/库/列都支持列不支持改用 DO
布尔类型TINYINT(1)BOOLEAN转换
字符串拼接CONCAT(a, b)a || b
NOW()关键字函数(必须带括号兼容
事务隔离REPEATABLE READ(默认)READ COMMITTED(默认)改 Spring 事务传播

5.4 pgloader 一键迁移

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 安装(Debian/Ubuntu)
apt install pgloader

# 配置文件
cat > mysql_to_pg.load << 'EOF'
LOAD DATABASE
    FROM      mysql://root:****@internal.example.com:3306/industry_iot_dev
    INTO postgresql://postgres:****@internal.example.com:5432/mine_dev

WITH include drop, create tables, no truncate, create indexes,
     reset sequences, foreign keys

ALTER schema 'industry_iot_dev' rename to 'mine_dev'
;
EOF

# 执行
pgloader mysql_to_pg.load

pgloader 的优势:自动建表、转换类型、处理字符集差异、迁移数据一气呵成。


6. 多租户改造:MySQL → PG + TimescaleDB

6.1 业务背景

某安全风险平台从 MySQL 8 迁到 PG 17,多租户数据隔离用 schema

  • 一个租户 = 一个 schema
  • 同一套代码,根据租户动态切换 schema
  • 时序数据从 InfluxDB 迁到 PG 的 TimescaleDB 插件

6.2 改造清单

系统层

1
2
3
1. 增加"租户管理"菜单
2. 域名区分租户(如 a.example.com → schema_a,b.example.com → schema_b)
3. 开关控制是否多租户(兼容单体部署)

数据层

1
2
3
4
- MySQL → PostgreSQL 17
- 去除 InfluxDB,时序数据存到 TimescaleDB
- 多租户数据隔离:每个租户一个 schema
- 动态路由:根据租户 ID 切换 schema

代码层

Spring Boot + MyBatis-Plus 动态 schema 切换

1
2
3
4
5
6
7
@Component
public class SchemaContext {
    private static final ThreadLocal<String> CURRENT_SCHEMA = new ThreadLocal<>();
    public static void set(String schema) { CURRENT_SCHEMA.set(schema); }
    public static String get() { return CURRENT_SCHEMA.get(); }
    public static void clear() { CURRENT_SCHEMA.remove(); }
}
1
2
3
4
5
6
7
8
@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        // AbstractRoutingDataSource + schema 路由
        return new TenantRoutingDataSource();
    }
}
1
2
3
4
<select id="selectRiskTask" resultType="RiskTask">
    SELECT * FROM "${schema}".risk_task WHERE id = #{id}
    <!-- schema 来自 SchemaContext.get() -->
</select>

6.3 TimescaleDB 集成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-- 1. 启用扩展
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- 2. 转为 hypertable
SELECT create_hypertable('sensor_data', 'time');

-- 3. 时序插入(每 30 秒一条)
INSERT INTO sensor_data (time, sensor_id, value)
VALUES (NOW(), 'sensor_001', 23.5);

-- 4. 查过去 1 小时
SELECT time_bucket('5 minutes', time) AS bucket,
       AVG(value) AS avg_value
FROM sensor_data
WHERE time > NOW() - INTERVAL '1 hour'
GROUP BY bucket
ORDER BY bucket;

核心收益:TimescaleDB 内部用 chunk 分区,自动压缩老数据,查询效率比 InfluxDB 高 5-10 倍——同一份时序数据存储成本只有 InfluxDB 的 1/5。

6.4 索引迁移

MySQLPostgreSQL
INDEX (col)CREATE INDEX ON table (col);
UNIQUE INDEX (col)CREATE UNIQUE INDEX ON table (col);
FULLTEXT INDEX不支持——用 pg_trgm 模糊匹配 + tsvector 全文检索
SPATIAL INDEXCREATE INDEX USING gist (col);(需 PostGIS)

6.5 触发器差异

维度MySQLPostgreSQL
语法CREATE TRIGGER ... BEFORE INSERT
触发器调用外部 HTTP复杂(要 UDF)原生 PL/Python + http 扩展
批量 FOR EACH STATEMENT不支持原生支持

经验总结

1. PG vs MySQL 选型

场景推荐
复杂查询(多表 JOIN、窗口函数)PG
高并发短查询MySQL
时序 + 关系混合PG + TimescaleDB(一库两用)
国产化替代PG(人大金仓、达梦都基于 PG 改)
简单 KV / 缓存MySQL 也行

2. 迁移铁律

  1. 先在测试环境跑通:用 pgloader 或 Navicat 至少完整跑 3 次
  2. DDL 必须手改:Navicat 不会帮你改 LIMIT offset, sizeLIMIT size OFFSET offset
  3. 应用层动态 schema 路由:用 ThreadLocal + AbstractRoutingDataSource
  4. 时序数据压缩:TimescaleDB 10x 压缩比能省 90% 磁盘
  5. 回归测试:业务代码必须全量回归——尤其是分页、事务、布尔判断

3. 多租户设计建议

  • 业务早期单库多 schema——比多库简单,迁移成本低
  • 租户多 / 数据量大时考虑多库路由
  • 永远不要public schema 存业务表——public 是 PG 默认搜索路径,混在一起会引发诡异的 bug

2024+ 视角:PostgreSQL 17 / 18 与 AI 时代新特性

PG 17(2024-09 发布)核心变化

  • 逻辑复制故障切换:内置 failover 命令,复制槽不再需手动重建
  • MERGE 语句完善:终于支持 RETURNING 子句 + WHEN NOT MATCHED BY SOURCE
  • json_table() 标准化:JSON 转换关系表,BI 报表直接用
  • WAL 压缩 / 增量备份:物理备份工具链 pg_basebackup 性能再升 30%
  • 真空进程优化:autovacuum 对大表不再是"压垮副本"的元凶

PG 18(2025 路线图)

  • 异步 I/O 子系统重构(AIO),顺序扫描性能 +30%
  • uuidv7() 内置(时间有序 UUID,替代 v4 解决 B-tree 碎片)
  • 改进的 B-tree 索引去重

pgvector:AI 时代的 PG 杀手锏

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-- 1. 启用(PG 16+ 官方扩展)
CREATE EXTENSION vector;

-- 2. 存向量
CREATE TABLE docs (
    id BIGSERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(1536)        -- OpenAI text-embedding-3 维度
);

-- 3. 查最相似(余弦距离)
SELECT id, content
FROM docs
ORDER BY embedding <=> '[0.1, 0.2, ...]'  -- '<=>' = cosine distance
LIMIT 5;

为什么选 pgvector 而不是专用向量库

  • 已经在用 PG 的话零成本增加"语义检索"能力
  • 事务、ACID、SQL 全部继承
  • 100 万向量以内性能可接受;千万级再考虑 Milvus / Qdrant

TimescaleDB 2.x 新变化

  • Continuous Aggregates 自动化:原需手写 add_continuous_aggregate_policy,2.x 简化为视图 + 自动刷新
  • 多节点(Multi-Node) GA:Access Node + Data Node 分层,集群横向扩展
  • hypercore 行存引擎:冷热数据分层——热数据行存 OLTP、冷数据列存 OLAP

Rust 生态进入 PG 生态

  • pgrx:用 Rust 写 PG 扩展,比 C 简单、内存安全。已有 Paradedb 用它实现 pg_lakehouse(Iceberg 集成)
  • pgcat:Rust 写的 PG 连接池,替代 pgBouncer 在大流量场景的瓶颈

PG 与 MySQL 在云时代的差距

维度20202024+
窗口函数 / CTEPG 已支持 8 年,MySQL 8.0 才追上持平
JSON 能力PG jsonb + 索引领先MySQL 8.0 仍弱于 PG
向量检索pgvector 杀手锏
云原生传统部署为主Aurora / AlloyDB / Cloud SQL PG 都内置 HA
HTAP需 Timescale / CitusAurora / PolarDB HTAP 一体化

结论:如果今天做新项目,PG 仍是关系型数据库首选——AI 时代的向量能力、云原生运维成熟度,让它对 MySQL 的领先从"略强"变成"明显"。


下一步

  • 多数据库适配架构:见《多数据库适配架构:MyBatis 方言 + JPA + Flyway》
  • 人大金仓国产数据库:见《人大金仓实战:国产数据库安装、迁移与 PG 兼容》
  • MySQL 索引优化 / 同步:见编程/数据库/mysql 系列
使用 Hugo 构建
主题 StackJimmy 设计