为什么写这篇:PostgreSQL 是最像 Oracle 的开源数据库——扩展生态强、SQL 严谨、ACID 完整。本文覆盖三件事:①配置调优(连接数、扩展);②日常 SQL(批量加主键、Truncate);③从 MySQL 8 迁移到 PostgreSQL 17 的多租户改造。
适用读者:需要在生产环境用 PG、要做 MySQL→PG 迁移的 DBA / 后端。
前置知识:会用 psql 客户端、懂 SQL、知道什么是 schema。
目录
- 连接数与运行时配置
- pg_stat_statements:SQL 性能分析扩展
- 批量加主键:生成 ALTER TABLE 脚本
- 多表 Truncate 与多模式管理
- 从 MySQL 8 迁移到 PostgreSQL 17
- 多租户改造: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;
|
| 字段 | 含义 |
|---|
query | SQL 模板(参数化) |
calls | 执行次数 |
total_time | 总耗时(ms) |
rows | 总返回行数 |
hit_percent | shared 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 数据传输 | 库到库迁移 | 图形化,但要手动建库 |
| pgloader | MySQL → 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 迁移后必须手改的差异点
| 维度 | MySQL | PostgreSQL | 改法 |
|---|
| 主键自增 | AUTO_INCREMENT | SERIAL 或 GENERATED BY DEFAULT AS IDENTITY | 重写 DDL |
| 时间戳默认值 | CURRENT_TIMESTAMP | CURRENT_TIMESTAMP | 兼容 |
| 字符集 | utf8mb4 | 数据库级设置 | 统一 |
| 关键字引号 | ` | " | 全局替换 |
| LIMIT 分页 | LIMIT offset, size | LIMIT 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 索引迁移
| MySQL | PostgreSQL |
|---|
INDEX (col) | CREATE INDEX ON table (col); |
UNIQUE INDEX (col) | CREATE UNIQUE INDEX ON table (col); |
FULLTEXT INDEX | 不支持——用 pg_trgm 模糊匹配 + tsvector 全文检索 |
SPATIAL INDEX | CREATE INDEX USING gist (col);(需 PostGIS) |
6.5 触发器差异
| 维度 | MySQL | PostgreSQL |
|---|
| 语法 | 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. 迁移铁律
- 先在测试环境跑通:用 pgloader 或 Navicat 至少完整跑 3 次
- DDL 必须手改:Navicat 不会帮你改
LIMIT offset, size → LIMIT size OFFSET offset - 应用层动态 schema 路由:用 ThreadLocal + AbstractRoutingDataSource
- 时序数据压缩:TimescaleDB 10x 压缩比能省 90% 磁盘
- 回归测试:业务代码必须全量回归——尤其是分页、事务、布尔判断
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 在云时代的差距
| 维度 | 2020 | 2024+ |
|---|
| 窗口函数 / CTE | PG 已支持 8 年,MySQL 8.0 才追上 | 持平 |
| JSON 能力 | PG jsonb + 索引领先 | MySQL 8.0 仍弱于 PG |
| 向量检索 | 无 | pgvector 杀手锏 |
| 云原生 | 传统部署为主 | Aurora / AlloyDB / Cloud SQL PG 都内置 HA |
| HTAP | 需 Timescale / Citus | Aurora / PolarDB HTAP 一体化 |
结论:如果今天做新项目,PG 仍是关系型数据库首选——AI 时代的向量能力、云原生运维成熟度,让它对 MySQL 的领先从"略强"变成"明显"。
下一步
- 多数据库适配架构:见《多数据库适配架构:MyBatis 方言 + JPA + Flyway》
- 人大金仓国产数据库:见《人大金仓实战:国产数据库安装、迁移与 PG 兼容》
- MySQL 索引优化 / 同步:见编程/数据库/mysql 系列