PostgreSQL 最佳实践
目录
快速入门
安装与版本
当前使用版本为 PostgreSQL 18.3。
macOS
# 使用 Homebrew 安装
brew install postgresql@18
# 启动服务
brew services start postgresql@18
# 或手动启动
pg_ctl -D /opt/homebrew/var/postgresql@18 start
# 连接
psql -U postgres -d postgresWindows
- 下载 PostgreSQL Windows 安装程序
- 运行
postgresql-18.x.x-1-windows-x64.exe - 选择安装目录和数据目录
- 设置密码
- 默认端口:5432
# 安装完成后使用 psql 连接
psql -U postgresLinux (Ubuntu)
# 创建 PostgreSQL 仓库配置文件
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
# 导入仓库签名密钥
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
# 更新并安装
sudo apt update
sudo apt install -y postgresql-18
# 启动服务
sudo systemctl start postgresql
sudo systemctl enable postgresql
# 连接(切换到 postgres 用户)
sudo -u postgres psqlDocker Compose 部署
services:
postgres:
image: postgres:18-alpine
container_name: postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: password123
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:启动:docker compose up -d
连接:psql -h localhost -U admin -d myapp
基本操作
-- 查看数据库
\l
-- 连接数据库
\c myapp
-- 查看表
\dt
-- 基本 CRUD
INSERT INTO users (name, email) VALUES ('Alice', '[email protected]');
SELECT * FROM users WHERE name = 'Alice';
UPDATE users SET age = 30 WHERE id = 1;
DELETE FROM users WHERE id = 1;Schema 设计
表设计原则
-- 使用适当的数据类型
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
age INTEGER CHECK (age >= 0),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 添加注释
COMMENT ON TABLE users IS '用户表';
COMMENT ON COLUMN users.id IS '用户唯一标识';关系设计
-- 外键约束
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
total DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status) WHERE status != 'completed';索引策略
索引类型
-- B-tree 索引(默认)
CREATE INDEX idx_users_email ON users(email);
-- 复合索引
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
-- 部分索引
CREATE INDEX idx_users_active ON users(email) WHERE deleted_at IS NULL;
-- GIN 索引(JSONB)
CREATE INDEX idx_products_data ON products USING GIN(data);
-- GiST 索引(全文搜索)
CREATE INDEX idx_articles_search ON articles USING GIST(to_tsvector('english', content));
-- 唯一索引
CREATE UNIQUE INDEX idx_users_username ON users(username);索引原则
| 原则 | 说明 |
|---|---|
| 选择性 | 基数高的字段建索引 |
| 查询模式 | 考虑 WHERE、JOIN、ORDER BY |
| 避免冗余 | 不要在已索引字段上再建函数索引 |
| 维护成本 | 索引影响写性能 |
表达式索引
-- 邮箱大小写不敏感
CREATE INDEX idx_users_email_lower ON users(LOWER(email));
-- 查询时直接使用
SELECT * FROM users WHERE LOWER(email) = LOWER('[email protected]');查询优化
EXPLAIN 分析
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5
ORDER BY order_count DESC
LIMIT 20;优化技巧
-- 避免 SELECT *
SELECT u.id, u.name FROM users u WHERE u.id = 1;
-- 使用 LIMIT 限制
SELECT * FROM orders LIMIT 100;
-- 使用 EXISTS 替代 IN
SELECT * FROM users u WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- 使用 CTE(公共表表达式)
WITH recent_orders AS (
SELECT user_id, SUM(total) as total
FROM orders
WHERE created_at > '2024-01-01'
GROUP BY user_id
)
SELECT u.name, r.total
FROM users u
JOIN recent_orders r ON u.id = r.user_id;JSONB 操作
PostgreSQL 内置 JSONB 支持:
-- 创建 JSONB 列
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
data JSONB
);
-- 插入
INSERT INTO products (name, data) VALUES (
'Widget',
'{"color": "red", "size": "large", "tags": ["new", "sale"]}'
);
-- 查询 JSONB
SELECT * FROM products WHERE data->>'color' = 'red';
-- 使用 GIN 索引查询
CREATE INDEX idx_products_data ON products USING GIN(data);
-- 包含查询
SELECT * FROM products WHERE data @> '{"color": "red"}';
-- 任意键查询
SELECT * FROM products WHERE data ? 'tags';
-- JSONB 遍历
SELECT id, jsonb_array_elements_text(data->'tags') as tag
FROM products;分区表
范围分区
-- 创建分区表
CREATE TABLE orders (
id BIGSERIAL,
user_id BIGINT NOT NULL,
total DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- 创建分区
CREATE TABLE orders_2024_q1 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
CREATE TABLE orders_2024_q2 PARTITION OF orders
FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');
-- 默认分区(捕获未匹配的数据)
CREATE TABLE orders_default PARTITION OF orders DEFAULT;列表分区
CREATE TABLE products (
id SERIAL,
name VARCHAR(255),
category VARCHAR(50),
PRIMARY KEY (id, category)
) PARTITION BY LIST (category);
CREATE TABLE products_electronics PARTITION OF products
FOR VALUES IN ('electronics', 'gadgets');
CREATE TABLE products_clothing PARTITION OF products
FOR VALUES IN ('clothing', 'shoes');事务和并发
事务隔离级别
-- READ COMMITTED(默认)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;行级锁
-- SELECT FOR UPDATE
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 执行更新
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- SKIP LOCKED(非阻塞)
SELECT * FROM orders
WHERE status = 'pending'
ORDER BY created_at
LIMIT 10
FOR UPDATE SKIP LOCKED;性能优化
VACUUM 和 ANALYZE
-- 手动 VACUUM
VACUUM ANALYZE users;
-- 查看表统计
SELECT schemaname, tablename, n_live_tup, n_dead_tup
FROM pg_stat_user_tables
WHERE tablename = 'users';连接池
-- 查看当前连接
SELECT count(*) FROM pg_stat_activity;
-- 查看最大连接数
SHOW max_connections;配置参数
-- 关键参数
ALTER SYSTEM SET shared_buffers = '4GB';
ALTER SYSTEM SET effective_cache_size = '12GB';
ALTER SYSTEM SET work_mem = '256MB';
ALTER SYSTEM SET maintenance_work_mem = '1GB';备份和恢复
逻辑备份
# 备份单个数据库
pg_dump -U postgres -F c myapp > myapp.dump
# 恢复
pg_restore -U postgres -d myapp myapp.dump
# 纯 SQL 导出
pg_dump -U postgres myapp > myapp.sql物理备份
# 使用 pg_basebackup
pg_basebackup -h localhost -U replication -D /backup -Ft -z -P常用函数
-- 字符串
SELECT CONCAT(first_name, ' ', last_name) FROM users;
SELECT UPPER(email);
SELECT LOWER(email);
SELECT TRIM(name);
-- 日期
SELECT NOW();
SELECT CURRENT_DATE;
SELECT DATE_TRUNC('month', created_at);
SELECT AGE(created_at); -- 距今天数
-- 聚合
SELECT COUNT(*), SUM(total), AVG(total), MIN(total), MAX(total)
FROM orders;
-- 窗口函数
SELECT
name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
SUM(salary) OVER (PARTITION BY department) as dept_total
FROM employees;