1. 为什么选择PostgreSQL与Docker组合
在当今云原生时代,Docker已经成为应用部署的标准工具之一。作为一名长期使用PostgreSQL的开发者,我发现将PostgreSQL部署在Docker容器中,能够带来诸多便利。PostgreSQL作为功能最强大的开源关系型数据库,与Docker的轻量级容器化技术相结合,可以快速搭建开发、测试和生产环境。
PostgreSQL 16是目前最新的稳定版本,相比MySQL,它在以下场景表现尤为出色:
- 复杂查询和数据分析:PostgreSQL的查询优化器更为先进
- JSON数据处理:内置的JSONB类型提供了完整的JSON支持
- 地理空间数据:通过PostGIS扩展支持专业的地理信息系统
- 自定义数据类型:允许开发者创建自己的数据类型和运算符
提示:虽然PostgreSQL功能强大,但对于简单的Web应用,MySQL可能仍然是更轻量级的选择。选择数据库时应该根据实际业务需求来决定。
2. 环境准备与项目初始化
2.1 Docker环境检查与安装
在开始之前,我们需要确保系统已经安装了Docker和Docker Compose。我推荐使用以下命令检查版本:
bash复制docker --version
docker-compose --version
如果系统尚未安装Docker Compose,可以使用以下命令安装(以Ubuntu/Debian为例):
bash复制sudo apt update
sudo apt install docker-compose-plugin -y
注意:较新的Docker版本已经将docker-compose集成到docker命令中,作为插件形式提供。建议使用
docker compose而不是旧的docker-compose命令。
2.2 镜像拉取与验证
我们将使用官方PostgreSQL 16镜像和pgAdmin 4镜像:
bash复制docker pull postgres:16
docker pull dpage/pgadmin4
验证镜像是否下载成功:
bash复制docker images | grep -E "postgres|pgadmin"
3. 项目目录结构与配置
3.1 创建项目目录
合理的目录结构是项目可维护性的基础。我建议按照以下方式组织项目:
bash复制mkdir -p ~/docker/postgres-project
cd ~/docker/postgres-project
mkdir -p postgres/{data,init}
mkdir -p pgadmin
chmod -R 777 pgadmin
目录结构说明:
code复制~/docker/postgres-project/
├── docker-compose.yml
├── postgres/
│ ├── data/ # 数据库数据文件(持久化存储)
│ └── init/ # 初始化SQL脚本
└── pgadmin/ # pgAdmin配置文件
3.2 初始化脚本编写
初始化脚本可以在容器首次启动时自动创建表结构和测试数据。这是一个非常实用的功能,特别是在开发环境中。
创建初始化脚本:
bash复制vim postgres/init/01-init.sql
以下是一个功能更完善的初始化脚本示例:
sql复制-- 启用常用扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- 创建产品表
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10,2) CHECK (price > 0),
stock INTEGER DEFAULT 0 CHECK (stock >= 0),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 创建用户表(带密码加密)
CREATE TABLE IF NOT EXISTS users (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
is_admin BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP WITH TIME ZONE
);
-- 创建订单表
CREATE TABLE IF NOT EXISTS orders (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
product_id INTEGER REFERENCES products(id) ON DELETE SET NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'cancelled')),
total_price DECIMAL(10,2),
order_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 创建索引
CREATE INDEX idx_products_name ON products(name);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_status ON orders(status);
-- 插入测试数据
INSERT INTO products (name, description, price, stock) VALUES
('智能手机', '最新款智能手机', 2999.00, 100),
('笔记本电脑', '高性能笔记本电脑', 5999.00, 50),
('无线耳机', '降噪无线耳机', 299.00, 200)
ON CONFLICT (name) DO NOTHING;
-- 触发器示例:更新时自动设置updated_at字段
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_products_timestamp
BEFORE UPDATE ON products
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
CREATE TRIGGER update_orders_timestamp
BEFORE UPDATE ON orders
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
这个脚本不仅创建了基本表结构,还包含了数据验证、触发器、索引等高级功能,更适合真实项目场景。
4. Docker Compose配置详解
4.1 完整的docker-compose.yml
以下是经过优化的docker-compose.yml配置:
yaml复制version: '3.8'
services:
postgres:
image: postgres:16
container_name: postgres16
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: ${DB_PASSWORD:-123456}
POSTGRES_DB: myapp
TZ: Asia/Shanghai
POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./postgres/data:/var/lib/postgresql/data
- ./postgres/init:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
interval: 5s
timeout: 5s
retries: 5
networks:
- postgres-network
pgadmin:
image: dpage/pgadmin4
container_name: pgadmin
restart: unless-stopped
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:-admin@admin.com}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-123456}
PGADMIN_CONFIG_LANGUAGE: 'zh_CN'
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: 'True'
PGADMIN_CONFIG_CONSOLE_LOG_LEVEL: '10'
volumes:
- ./pgadmin:/var/lib/pgadmin
depends_on:
postgres:
condition: service_healthy
networks:
- postgres-network
networks:
postgres-network:
driver: bridge
4.2 关键配置解析
-
环境变量安全:
- 使用
${VAR:-default}语法,允许通过环境变量文件或命令行覆盖默认值 - 建议在生产环境中使用
.env文件管理敏感信息
- 使用
-
健康检查:
- 添加了PostgreSQL健康检查,确保pgAdmin只在数据库就绪后启动
- 使用
pg_isready命令检查数据库状态
-
数据库初始化优化:
- 通过
POSTGRES_INITDB_ARGS设置数据库编码和排序规则 - 明确指定
PGDATA路径,避免潜在冲突
- 通过
-
pgAdmin安全增强:
- 启用了增强的cookie保护
- 设置了详细的日志级别
-
重启策略:
- 使用
unless-stopped而不是always,提供更灵活的控制
- 使用
5. 服务启动与管理
5.1 启动服务
使用以下命令启动所有服务:
bash复制cd ~/docker/postgres-project
docker compose up -d
提示:较新的Docker版本推荐使用
docker compose(不带横线)命令,它是原生命令,功能更丰富。
5.2 验证服务状态
检查容器运行状态:
bash复制docker compose ps
查看PostgreSQL日志:
bash复制docker compose logs -f postgres
5.3 数据库连接测试
进入PostgreSQL容器执行SQL:
bash复制docker compose exec postgres psql -U admin -d myapp -c "SELECT * FROM products;"
或者直接进入交互式psql:
bash复制docker compose exec postgres psql -U admin -d myapp
6. pgAdmin配置与使用技巧
6.1 访问pgAdmin
在浏览器中访问:
code复制http://localhost:5050
或
code复制http://<服务器IP>:5050
使用以下凭据登录:
- 邮箱:admin@admin.com
- 密码:123456
6.2 添加服务器连接
- 右键"Servers" → "Create" → "Server"
- 在"General"标签页设置名称(如"My PostgreSQL")
- 在"Connection"标签页填写:
- Host name/address:
postgres(使用Docker服务名) - Port:
5432 - Maintenance database:
myapp - Username:
admin - Password:
123456
- Host name/address:
注意:在Docker环境中,容器间通信应该使用服务名而不是IP地址,这样更可靠且不受IP变化影响。
6.3 高级功能使用
-
查询工具:
- 支持语法高亮、自动完成
- 可以保存常用查询片段
-
数据导出/导入:
- 支持多种格式:CSV、JSON、SQL等
- 可以设置导出选项,如包含列名、引号等
-
仪表盘监控:
- 查看数据库活动会话
- 监控锁等待和长时间运行的查询
-
备份与恢复:
- 提供图形化界面进行数据库备份
- 支持定时备份任务设置
7. 远程连接配置与安全
7.1 防火墙配置
如果从外部访问,需要开放相应端口:
bash复制sudo ufw allow 5432/tcp
sudo ufw allow 5050/tcp
sudo ufw reload
对于云服务器,还需要在云平台的安全组中开放这些端口。
7.2 客户端连接配置
推荐使用以下客户端工具连接PostgreSQL:
-
DBeaver:
- 免费开源
- 支持多种数据库
- 强大的数据编辑和导出功能
-
DataGrip:
- JetBrains出品
- 智能代码补全
- 强大的重构工具
连接参数:
- 主机:服务器IP
- 端口:5432
- 数据库:myapp
- 用户名:admin
- 密码:123456
7.3 安全增强建议
-
修改默认密码:
- 强烈建议修改admin用户的默认密码
- 可以在docker-compose.yml中设置更复杂的密码
-
限制访问IP:
- 在生产环境中,应该限制可以访问数据库的IP
- 可以通过防火墙或PostgreSQL的pg_hba.conf实现
-
使用SSL加密:
- 对于生产环境,应该启用SSL加密连接
- 可以在PostgreSQL配置中设置SSL证书
-
定期备份:
- 设置自动备份策略
- 考虑使用WAL归档实现时间点恢复
8. 备份与恢复策略
8.1 基础备份方法
- 使用pg_dump:
bash复制docker compose exec postgres pg_dump -U admin -F c -b -v -f /tmp/backup.dump myapp
docker compose cp postgres:/tmp/backup.dump ./postgres-backup.dump
- 备份整个数据目录:
bash复制# 停止容器
docker compose stop postgres
# 备份数据目录
tar czvf postgres-data-backup.tar.gz ./postgres/data
# 启动容器
docker compose start postgres
8.2 自动化备份脚本
创建backup.sh脚本:
bash复制#!/bin/bash
BACKUP_DIR="/path/to/backups"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="myapp"
USER="admin"
# 创建备份目录
mkdir -p ${BACKUP_DIR}
# 执行备份
docker compose exec -T postgres pg_dump -U ${USER} -F c -b -v -f /tmp/${DB_NAME}_${DATE}.dump ${DB_NAME}
# 将备份文件复制到宿主机
docker compose cp postgres:/tmp/${DB_NAME}_${DATE}.dump ${BACKUP_DIR}/
# 删除容器内的临时文件
docker compose exec postgres rm -f /tmp/${DB_NAME}_${DATE}.dump
# 保留最近7天的备份
find ${BACKUP_DIR} -name "${DB_NAME}_*.dump" -type f -mtime +7 -delete
echo "Backup completed: ${BACKUP_DIR}/${DB_NAME}_${DATE}.dump"
设置定时任务(每天凌晨2点执行):
bash复制crontab -e
添加:
code复制0 2 * * * /path/to/backup.sh >> /var/log/postgres-backup.log 2>&1
8.3 恢复备份
- 从dump文件恢复:
bash复制docker compose cp ./postgres-backup.dump postgres:/tmp/restore.dump
docker compose exec postgres pg_restore -U admin -c -d myapp /tmp/restore.dump
- 从SQL文件恢复:
bash复制docker compose exec -T postgres psql -U admin -d myapp < backup.sql
9. 性能优化与监控
9.1 PostgreSQL配置调优
编辑postgresql.conf(需要进入容器):
bash复制docker compose exec postgres bash
vi /var/lib/postgresql/data/pgdata/postgresql.conf
关键参数建议:
ini复制# 内存相关
shared_buffers = 1GB # 25% of total RAM
work_mem = 16MB # 用于排序操作的内存
maintenance_work_mem = 256MB # 维护操作的内存
# WAL配置
wal_level = replica
synchronous_commit = on
wal_buffers = 16MB
# 并行查询
max_worker_processes = 8
max_parallel_workers_per_gather = 4
# 连接数
max_connections = 100
# 自动清理
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 1min
修改后重启容器使配置生效:
bash复制docker compose restart postgres
9.2 监控工具集成
- pg_stat_statements扩展:
sql复制CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
- 查询性能分析:
sql复制SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-
使用pgAdmin监控:
- 仪表盘提供实时查询监控
- 可以查看锁等待和阻塞情况
-
Prometheus + Grafana:
- 使用postgres_exporter收集指标
- 在Grafana中配置PostgreSQL监控面板
10. 常见问题与解决方案
10.1 容器启动失败
问题现象:
code复制Error: Database is uninitialized and superuser password is not specified.
解决方案:
- 确保docker-compose.yml中设置了
POSTGRES_PASSWORD - 检查数据目录权限:
bash复制chmod -R 777 ./postgres/data
10.2 连接超时
问题现象:
code复制psql: could not connect to server: Connection timed out
解决方案:
-
检查PostgreSQL是否监听正确地址:
bash复制docker compose exec postgres cat /var/lib/postgresql/data/pgdata/postgresql.conf | grep listen_addresses应该显示
listen_addresses = '*' -
检查pg_hba.conf配置:
bash复制docker compose exec postgres cat /var/lib/postgresql/data/pgdata/pg_hba.conf确保有类似以下行:
code复制host all all 0.0.0.0/0 md5
10.3 pgAdmin连接问题
问题现象:
code复制Unable to connect to server: could not translate host name "postgres" to address
解决方案:
- 确保pgAdmin和PostgreSQL在同一个Docker网络中
- 检查网络配置:
bash复制
docker network inspect postgres-project_postgres-network - 尝试使用容器IP代替服务名
10.4 数据持久化失败
问题现象:
重启容器后数据丢失
解决方案:
- 检查volume挂载是否正确:
bash复制
docker inspect postgres16 | grep Mounts - 确保数据目录有正确权限:
bash复制chmod -R 777 ./postgres/data - 检查docker-compose.yml中的volumes配置
10.5 性能问题
问题现象:
查询速度慢,响应时间长
解决方案:
- 检查慢查询:
sql复制SELECT * FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5; - 添加适当的索引
- 优化查询语句,避免全表扫描
- 考虑调整PostgreSQL配置参数
11. 生产环境部署建议
11.1 安全加固措施
-
使用专用用户:
- 不要使用超级用户admin进行日常操作
- 为每个应用创建专用用户并限制权限
-
网络隔离:
- 将PostgreSQL放在内部网络
- 只允许应用服务器访问数据库端口
-
定期更新:
- 定期更新PostgreSQL和Docker镜像
- 关注安全公告和补丁
-
审计日志:
ini复制# 在postgresql.conf中启用审计 log_statement = 'all' log_destination = 'csvlog' logging_collector = on
11.2 高可用方案
-
主从复制:
- 配置流复制(Streaming Replication)
- 使用pgpool-II实现负载均衡
-
容器编排:
- 使用Kubernetes管理PostgreSQL集群
- 考虑使用StatefulSet和有状态服务
-
备份策略:
- 每日全量备份+WAL归档
- 定期测试恢复流程
11.3 资源限制
在docker-compose.yml中为容器设置资源限制:
yaml复制services:
postgres:
deploy:
resources:
limits:
cpus: '2'
memory: 4G
reservations:
memory: 2G
12. 进阶主题与扩展
12.1 使用扩展(Extensions)
PostgreSQL的强大之处在于其丰富的扩展生态系统:
sql复制-- 常用扩展
CREATE EXTENSION IF NOT EXISTS hstore; -- 键值对存储
CREATE EXTENSION IF NOT EXISTS postgis; -- 地理空间数据
CREATE EXTENSION IF NOT EXISTS timescaledb; -- 时序数据
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- 模糊搜索
12.2 分表与分区
对于大型表,考虑使用分区表提高性能:
sql复制-- 创建分区表
CREATE TABLE measurement (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
) PARTITION BY RANGE (logdate);
-- 创建分区
CREATE TABLE measurement_y2023m01 PARTITION OF measurement
FOR VALUES FROM ('2023-01-01') TO ('2023-02-01');
12.3 逻辑复制
配置发布/订阅实现逻辑复制:
sql复制-- 在发布者节点
CREATE PUBLICATION mypub FOR TABLE users, products;
-- 在订阅者节点
CREATE SUBSCRIPTION mysub
CONNECTION 'host=publisher dbname=myapp user=repuser password=secret'
PUBLICATION mypub;
12.4 使用TimescaleDB
对于时序数据,可以添加TimescaleDB扩展:
sql复制-- 创建超表
CREATE TABLE conditions (
time TIMESTAMPTZ NOT NULL,
location TEXT NOT NULL,
temperature DOUBLE PRECISION NULL,
humidity DOUBLE PRECISION NULL
);
SELECT create_hypertable('conditions', 'time');
13. 容器化最佳实践
13.1 镜像构建优化
如果需要自定义PostgreSQL镜像,建议使用多阶段构建:
dockerfile复制FROM postgres:16 as builder
# 安装编译依赖
RUN apt-get update && apt-get install -y \
build-essential \
postgresql-server-dev-16
# 编译安装扩展
WORKDIR /tmp
RUN git clone https://github.com/timescale/timescaledb.git
WORKDIR /tmp/timescaledb
RUN ./bootstrap && make -C build && make -C build install
FROM postgres:16
# 从builder阶段复制已编译的扩展
COPY --from=builder /usr/lib/postgresql/16/lib/timescaledb*.so /usr/lib/postgresql/16/lib/
COPY --from=builder /usr/share/postgresql/16/extension/timescaledb* /usr/share/postgresql/16/extension/
# 复制初始化脚本
COPY init.sql /docker-entrypoint-initdb.d/
13.2 健康检查策略
在docker-compose.yml中添加更完善的健康检查:
yaml复制healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
13.3 资源监控
使用cAdvisor监控容器资源使用情况:
bash复制docker run \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--volume=/dev/disk/:/dev/disk:ro \
--publish=8080:8080 \
--detach=true \
--name=cadvisor \
gcr.io/google-containers/cadvisor:latest
14. 迁移与升级策略
14.1 从MySQL迁移
- 使用pgloader工具:
bash复制docker run --rm -it dimitri/pgloader \
pgloader mysql://user:pass@mysql-host/dbname \
postgresql://user:pass@postgres-host/dbname
- 手动迁移步骤:
- 导出MySQL数据为CSV
- 在PostgreSQL中创建相应表结构
- 使用COPY命令导入数据
14.2 PostgreSQL版本升级
-
使用pg_dump/pg_restore:
- 从旧版本导出数据
- 在新版本中恢复
-
使用pg_upgrade:
bash复制# 停止旧版本 docker stop postgres-old # 运行升级 docker run --rm \ -v /path/to/old/data:/var/lib/postgresql/old/data \ -v /path/to/new/data:/var/lib/postgresql/new/data \ postgres:16 pg_upgrade \ -b /usr/lib/postgresql/15/bin \ -B /usr/lib/postgresql/16/bin \ -d /var/lib/postgresql/old/data \ -D /var/lib/postgresql/new/data
15. 实际应用案例
15.1 电商系统数据库设计
sql复制-- 产品分类
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
parent_id INTEGER REFERENCES categories(id),
is_active BOOLEAN DEFAULT TRUE
);
-- 产品属性
CREATE TABLE attributes (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL
);
-- 产品属性值
CREATE TABLE attribute_values (
id SERIAL PRIMARY KEY,
attribute_id INTEGER NOT NULL REFERENCES attributes(id),
value VARCHAR(255) NOT NULL,
UNIQUE (attribute_id, value)
);
-- 产品表
CREATE TABLE products (
id SERIAL PRIMARY KEY,
category_id INTEGER NOT NULL REFERENCES categories(id),
sku VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL CHECK (price > 0),
compare_at_price DECIMAL(10,2),
cost_price DECIMAL(10,2),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- 产品-属性关联
CREATE TABLE product_attributes (
product_id INTEGER NOT NULL REFERENCES products(id),
attribute_value_id INTEGER NOT NULL REFERENCES attribute_values(id),
PRIMARY KEY (product_id, attribute_value_id)
);
-- 库存管理
CREATE TABLE inventory (
product_id INTEGER PRIMARY KEY REFERENCES products(id),
quantity INTEGER NOT NULL DEFAULT 0 CHECK (quantity >= 0),
low_stock_threshold INTEGER DEFAULT 5
);
-- 客户表
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
phone VARCHAR(50),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- 订单表
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
order_number VARCHAR(50) UNIQUE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
total DECIMAL(10,2) NOT NULL,
subtotal DECIMAL(10,2) NOT NULL,
tax DECIMAL(10,2) NOT NULL DEFAULT 0,
shipping_cost DECIMAL(10,2) NOT NULL DEFAULT 0,
discount DECIMAL(10,2) NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- 订单项
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(id),
product_id INTEGER NOT NULL REFERENCES products(id),
price DECIMAL(10,2) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
total DECIMAL(10,2) GENERATED ALWAYS AS (price * quantity) STORED
);
-- 支付记录
CREATE TABLE payments (
id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(id),
amount DECIMAL(10,2) NOT NULL,
payment_method VARCHAR(50) NOT NULL,
transaction_id VARCHAR(255),
status VARCHAR(20) NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
15.2 数据分析查询示例
sql复制-- 月度销售报告
SELECT
DATE_TRUNC('month', o.created_at) AS month,
COUNT(DISTINCT o.id) AS order_count,
COUNT(oi.id) AS item_count,
SUM(o.total) AS total_sales,
AVG(o.total) AS avg_order_value,
SUM(oi.quantity) AS total_units_sold
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.status = 'completed'
GROUP BY month
ORDER BY month;
-- 产品热销排名
SELECT
p.id,
p.name,
p.price,
SUM(oi.quantity) AS total_sold,
SUM(oi.total) AS total_revenue,
i.quantity AS current_stock
FROM products p
JOIN order_items oi ON p.id = oi.product_id
JOIN orders o ON oi.order_id = o.id
JOIN inventory i ON p.id = i.product_id
WHERE o.status = 'completed'
GROUP BY p.id, p.name, p.price, i.quantity
ORDER BY total_sold DESC
LIMIT 10;
-- 客户价值分析
SELECT
c.id,
c.email,
c.first_name || ' ' || c.last_name AS customer_name,
COUNT(o.id) AS order_count,
SUM(o.total) AS lifetime_value,
MAX(o.created_at) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.email, customer_name
ORDER BY lifetime_value DESC NULLS LAST;
16. 性能调优实战
16.1 查询优化案例
慢查询示例:
sql复制EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id IN (
SELECT id FROM customers
WHERE email LIKE '%@gmail.com'
)
AND status = 'completed'
ORDER BY created_at DESC;
优化方案:
- 添加索引:
sql复制CREATE INDEX idx_customers_email ON customers(email);
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status);
- 重写查询:
sql复制EXPLAIN ANALYZE
SELECT o.* FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.email LIKE '%@gmail.com'
AND o.status = 'completed'
ORDER BY o.created_at DESC;
16.2 连接池配置
使用PgBouncer作为连接池:
yaml复制# docker-compose.yml
services:
pgbouncer:
image: edoburu/pgbouncer
environment:
DATABASE_URL: postgres://admin:123456@postgres:5432/myapp
POOL_MODE: transaction
MAX_CLIENT_CONN: 100
DEFAULT_POOL_SIZE: 20
ports:
- "6432:5432"
depends_on:
- postgres
networks:
- postgres-network
应用连接字符串改为:
code复制postgres://admin:123456@pgbouncer:5432/myapp
16.3 分区表性能测试
创建分区表并测试性能:
sql复制-- 创建分区表
CREATE TABLE sensor_data (
sensor_id INTEGER,
recorded_at TIMESTAMPTZ,
value DOUBLE PRECISION
) PARTITION BY RANGE (recorded_at);
-- 创建每月分区
CREATE TABLE sensor_data_202301 PARTITION OF sensor_data
FOR VALUES FROM ('2023-01-01') TO ('2023-02-01');
-- 插入测试数据
INSERT INTO sensor_data
SELECT
random()*100,
timestamp '2023-01-01' + random() * interval '31 days',
random()*100
FROM generate_series(1, 1000000);
-- 查询性能比较
EXPLAIN ANALYZE SELECT * FROM sensor_data WHERE recorded_at BETWEEN '2023-01-15' AND '2023-01-20';
17. 安全最佳实践
17.1 最小权限原则
为应用创建专用用户:
sql复制CREATE ROLE app_user WITH LOGIN PASSWORD 'securepassword';
GRANT CONNECT ON DATABASE myapp TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
17.2 审计日志配置
在postgresql.conf中:
ini复制log_statement = 'mod' # 记录所有数据修改语句
log_connections = on
log_disconnections = on
log_line_prefix = '%m [%p] %q%u@%d '
log_timezone = 'Asia/Shanghai'
17.3 定期安全检查清单
- 检查未使用的账户:
sql复制SELECT usename FROM pg_user WHERE usename NOT IN (
SELECT DISTINCT usename FROM pg_stat_activity
) AND usename NOT LIKE 'pg_%';
- 检查过度权限:
sql复制SELECT grantee, privilege_type, table_name
FROM information_schema.role_table_grants
WHERE grantee NOT IN ('postgres', 'admin');
- 检查弱密码:
sql复制SELECT usename, passwd FROM pg_shadow WHERE passwd IS NULL OR passwd = '';
18. 扩展阅读与资源
18.1 官方文档
18.2 推荐书籍
- "PostgreSQL 16 Administration Cookbook" - Simon Riggs
- "Mastering PostgreSQL 16" - Hans-Jürgen Schönig
- "The Art of PostgreSQL" - Dimitri Fontaine
18.3 在线课程
- PostgreSQL for Everybody - Coursera
- Advanced PostgreSQL - Udemy
- PostgreSQL Performance Tuning - Pluralsight
18.4 社区资源
19. 个人经验分享
在实际项目中部署PostgreSQL容器时,我总结了以下几点经验:
-
初始化脚本管理:
- 将大型初始化脚本拆分为多个小文件,按数字前缀排序执行
- 使用
IF NOT EXISTS避免重复创建对象 - 在脚本中添加注释说明每个对象的用途
-
数据迁移策略:
- 对于生产环境,先在小规模测试环境验证迁移过程
- 考虑使用逻辑复制实现零停机迁移
- 迁移后务必进行数据一致性检查
-
性能监控:
- 设置警报监控长时间运行的查询
- 定期检查索引使用情况,移除未使用的索引
- 使用
EXPLAIN ANALYZE分析关键查询性能
-
备份验证:
- 定期测试备份恢复流程
- 考虑使用PITR(Point-in-Time Recovery)实现精细恢复
- 将备份存储在异地以确保灾难恢复
-
扩展规划:
- 设计时考虑未来可能的扩展需求
- 使用分区表处理大型数据集
- 考虑读写分离架构分担负载