🐘

PostgreSQL 完全学习指南

世界上最先进的开源关系型数据库

🗄️ 关系型数据库 🔒 ACID事务 📊 丰富扩展 🌍 开源免费 🚀 高性能

📖 PostgreSQL 简介

PostgreSQL(简称Postgres)是功能强大的开源关系型数据库管理系统,支持SQL标准,提供ACID事务、外键、触发器、视图、存储过程等高级特性。

✨ 核心特性

  • ✅ 完整的ACID事务支持
  • ✅ 丰富的索引类型(B-tree、Hash、GiST等)
  • ✅ JSON/JSONB原生支持
  • ✅ 窗口函数和CTE递归查询
  • ✅ 强大的扩展生态系统
  • ✅ 表分区和物化视图

🎯 应用场景

  • 🌐 Web应用后端和API服务
  • 📍 地理位置数据(PostGIS)
  • 📊 数据分析和BI平台
  • 📈 时间序列数据(TimescaleDB)
  • 📝 内容管理系统
  • 🔬 科学计算和研究

🔌 主要扩展

PostGIS

空间和地理对象支持

TimescaleDB

时间序列数据优化

pg_trgm

模糊文本搜索

pgcrypto

加密功能

hstore

键值对存储

pg_stat_statements

查询性能统计

🔧 PostgreSQL 安装和配置

🍎 macOS 安装

# 使用 Homebrew
brew install postgresql@14

# 启动服务
brew services start postgresql@14

# 初始化数据库(如果首次安装)
initdb /usr/local/var/postgres

# 创建数据库
createdb mydb

# 连接
psql mydb

🐧 Linux 安装

# Ubuntu/Debian
sudo apt update
sudo apt install postgresql postgresql-contrib

# 启动服务
sudo systemctl start postgresql
sudo systemctl enable postgresql

# 切换用户并连接
sudo -u postgres psql

# 修改postgres用户密码
ALTER USER postgres PASSWORD 'newpassword';

# 创建数据库
createdb mydb

# 配置远程访问(编辑 postgresql.conf)
listen_addresses = '*'

⚙️ 配置文件 postgresql.conf

# 连接设置
listen_addresses = '*'
port = 5432
max_connections = 200

# 内存设置
shared_buffers = 4GB
effective_cache_size = 12GB
work_mem = 64MB
maintenance_work_mem = 1GB

# WAL设置
wal_buffers = 16MB
checkpoint_completion_target = 0.9
max_wal_size = 2GB
min_wal_size = 1GB

# 日志设置
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d.log'
log_statement = 'all'

# 查询性能
random_page_cost = 1.1
effective_io_concurrency = 200

配置文件位置:/etc/postgresql/14/main/postgresql.conf

🐳 Docker 部署

# 拉取镜像
docker pull postgres:14

# 运行容器
docker run --name postgres \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -v /data/postgres:/var/lib/postgresql/data \
  -d postgres:14

# 连接
docker exec -it postgres psql -U postgres

# 带扩展的镜像
docker pull timescale/timescaledb:latest-pg14

# Docker Compose
version: '3.8'
services:
  postgres:
    image: postgres:14
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    ports:
      - "5432:5432"
    volumes:
      - ./data:/var/lib/postgresql/data

💡 PostgreSQL 基础语法

📊 常用数据类型

数值类型

  • SMALLINT - 2字节整数
  • INTEGER - 4字节整数
  • BIGINT - 8字节整数
  • DECIMAL(10,2) - 精确小数
  • REAL - 单精度浮点
  • DOUBLE PRECISION - 双精度浮点
  • NUMERIC - 任意精度数字

字符串类型

  • CHAR(n) - 定长字符串
  • VARCHAR(n) - 变长字符串
  • TEXT - 无限长文本
  • UUID - 全局唯一标识符
  • BYTEA - 二进制数据

日期时间

  • DATE - 日期
  • TIME - 时间
  • TIMESTAMP - 日期时间
  • INTERVAL - 时间间隔

其他类型

  • BOOLEAN - 布尔值
  • JSON / JSONB - JSON数据
  • ARRAY[] - 数组
  • XML - XML数据

🏗️ DDL - 数据定义语言

-- 创建数据库
CREATE DATABASE mydb 
    WITH ENCODING 'UTF8'
    LC_COLLATE = 'en_US.UTF-8'
    LC_CTYPE = 'en_US.UTF-8';

-- 使用数据库
\c mydb

-- 创建表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    age INTEGER CHECK (age >= 0 AND age <= 150),
    status VARCHAR(20) DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建索引
CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_email ON users(email);
CREATE UNIQUE INDEX idx_email_unique ON users(email);

-- 修改表
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users RENAME COLUMN age TO age_years;
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING age::INTEGER;

-- 删除表
DROP TABLE IF EXISTS users CASCADE;

📝 DML - 数据操作语言

-- 插入数据
INSERT INTO users (username, email, password_hash, age) 
VALUES 
    ('john', 'john@example.com', 'hash123', 25),
    ('jane', 'jane@example.com', 'hash456', 30);

-- 查询数据
SELECT * FROM users;
SELECT username, email FROM users WHERE age > 25;
SELECT username, COUNT(*) FROM users GROUP BY username;

-- 更新数据
UPDATE users 
SET email = 'newemail@example.com' 
WHERE username = 'john';

-- 删除数据
DELETE FROM users WHERE status = 'inactive';

-- 事务
BEGIN;
    INSERT INTO users (username, email) VALUES ('test', 'test@example.com');
    UPDATE users SET age = 29 WHERE username = 'test';
COMMIT;

ROLLBACK;  -- 回滚

📄 JSON/JSONB支持

-- 创建JSONB字段
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    specs JSONB
);

-- 插入JSON数据
INSERT INTO products (name, specs) VALUES 
('iPhone', '{"storage": "128GB", "color": "black", "price": 999}');

-- 查询JSON
SELECT specs->>'color' FROM products;
SELECT specs->'price' FROM products;

-- JSON运算符
SELECT * FROM products WHERE specs @> '{"color": "black"}';
SELECT * FROM products WHERE specs ? 'price';
SELECT * FROM products WHERE specs ?| ARRAY['color', 'storage'];

-- JSONB索引(GIN索引)
CREATE INDEX idx_specs ON products USING GIN (specs);

-- JSON函数
SELECT jsonb_pretty(specs) FROM products;
SELECT jsonb_extract_path(specs, 'storage', '128GB') FROM products;

📋 数组操作

-- 创建带数组的表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    tags VARCHAR[] DEFAULT '{}',
    colors TEXT[],
    prices NUMERIC[]
);

-- 插入数组数据
INSERT INTO products (name, tags, colors) VALUES 
('iPhone', ARRAY['mobile', 'electronics'], 
 ARRAY['black', 'white', 'blue']);

-- 查询数组
SELECT * FROM products WHERE 'electronics' = ANY(tags);
SELECT * FROM products WHERE tags @> ARRAY['mobile'];
SELECT unnest(tags) FROM products;

-- 数组函数
SELECT array_length(tags, 1) FROM products;
SELECT array_append(tags, 'new-tag') FROM products;
SELECT array_cat(tags, ARRAY['tag1', 'tag2']) FROM products;

⚡ PostgreSQL 高级特性

🪟 窗口函数

-- ROW_NUMBER、RANK、DENSE_RANK
SELECT 
    name,
    salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) as row_num,
    RANK() OVER (ORDER BY salary DESC) as rank,
    DENSE_RANK() OVER (ORDER BY salary DESC) as dense_rank
FROM employees;

-- 分区内排名
SELECT 
    department,
    name,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank
FROM employees;

-- 累计统计
SELECT 
    date,
    sales,
    SUM(sales) OVER (ORDER BY date) as cumulative_sales,
    AVG(sales) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as moving_avg
FROM daily_sales;

-- LAG/LEAD
SELECT 
    date,
    sales,
    LAG(sales, 1) OVER (ORDER BY date) as prev_sales,
    LEAD(sales, 1) OVER (ORDER BY date) as next_sales
FROM daily_sales;

🌳 CTE递归查询

-- 递归查询树形结构
WITH RECURSIVE categories AS (
    -- 锚点:顶级分类
    SELECT id, name, parent_id, 0 as level
    FROM category 
    WHERE parent_id IS NULL
    
    UNION ALL
    
    -- 递归:子分类
    SELECT c.id, c.name, c.parent_id, cat.level + 1
    FROM category c
    INNER JOIN categories cat ON c.parent_id = cat.id
)
SELECT * FROM categories;

-- 路径追踪
WITH RECURSIVE employee_hierarchy AS (
    SELECT id, name, manager_id, name as path
    FROM employees 
    WHERE manager_id IS NULL
    
    UNION ALL
    
    SELECT e.id, e.name, e.manager_id, 
           eh.path || ' > ' || e.name
    FROM employees e
    INNER JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM employee_hierarchy;

📚 丰富的索引类型

-- B-tree索引(默认)
CREATE INDEX idx_email ON users(email);

-- GiST索引(地理数据)
CREATE INDEX idx_location ON places USING GiST (location);

-- GIN索引(JSONB、数组)
CREATE INDEX idx_specs ON products USING GIN (specs);
CREATE INDEX idx_tags ON products USING GIN (tags);

-- BRIN索引(大表)
CREATE INDEX idx_timestamp ON logs USING BRIN (created_at);

-- 部分索引
CREATE INDEX idx_active_users ON users(email) 
WHERE status = 'active';

-- 表达式索引
CREATE INDEX idx_lower_email ON users(lower(email));

-- 复合索引
CREATE INDEX idx_name_age ON users(lastname, firstname, age);

⚙️ 存储过程和触发器

-- 创建存储过程
CREATE OR REPLACE FUNCTION get_user_orders(
    user_id INTEGER,
    OUT order_count INTEGER,
    OUT total_amount NUMERIC
) AS $$
BEGIN
    SELECT COUNT(*), SUM(amount)
    INTO order_count, total_amount
    FROM orders 
    WHERE user_id = get_user_orders.user_id;
END;
$$ LANGUAGE plpgsql;

-- 调用存储过程
SELECT * FROM get_user_orders(1);

-- 创建触发器
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_users_timestamp
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();

🔀 表分区

-- 范围分区
CREATE TABLE orders (
    id SERIAL,
    order_date DATE,
    amount NUMERIC
) PARTITION BY RANGE (order_date);

-- 创建分区表
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 users (
    id SERIAL,
    username VARCHAR(50)
) PARTITION BY HASH (id);

CREATE TABLE users_p0 PARTITION OF users 
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE users_p1 PARTITION OF users 
FOR VALUES WITH (MODULUS 4, REMAINDER 1);

🚀 PostgreSQL 性能优化

⚡ 查询优化技巧

✅ 最佳实践

  • • 使用EXPLAIN分析查询计划
  • • 为WHERE、JOIN创建索引
  • • 使用LIMIT限制结果集
  • • 避免SELECT *
  • • 使用连接池(pgBouncer)
  • • 合理配置工作内存

❌ 避免的陷阱

  • • 避免全表扫描
  • • 不要过度使用子查询
  • • 避免函数在WHERE子句
  • • 不要忽略统计信息
  • • 避免表膨胀
  • • 不要乱用CURSOR

🔍 EXPLAIN分析

-- 分析查询计划
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';

-- 详细执行统计
EXPLAIN (ANALYZE, BUFFERS, VERBOSE) 
SELECT u.name, COUNT(o.id) 
FROM users u 
JOIN orders o ON u.id = o.user_id
GROUP BY u.name;

-- JSON格式输出
EXPLAIN (FORMAT JSON, ANALYZE) 
SELECT * FROM products WHERE specs @> '{"color": "black"}';

-- 常见计划节点
-- Seq Scan - 顺序扫描(避免)
-- Index Scan - 索引扫描(推荐)
-- Bitmap Scan - 位图扫描
-- Nested Loop - 嵌套循环
-- Hash Join - 哈希连接
-- Sort - 排序

⚙️ 关键配置参数

# 内存配置(服务器8GB RAM示例)
shared_buffers = 2GB              # 25% RAM
effective_cache_size = 6GB         # 50-75% RAM
work_mem = 64MB                    # 每个操作
maintenance_work_mem = 1GB         # 维护操作

# WAL配置
wal_buffers = 16MB
checkpoint_completion_target = 0.9
max_wal_size = 2GB
min_wal_size = 1GB

# 连接配置
max_connections = 200
shared_buffers = max_connections * 2MB

# 查询优化
random_page_cost = 1.1              # SSD环境
effective_io_concurrency = 200      # SSD环境
default_statistics_target = 100

# 并行查询
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
max_worker_processes = 8

📊 性能监控

-- 查看连接状态
SELECT * FROM pg_stat_activity;

-- 查看慢查询
SELECT * FROM pg_stat_statements 
ORDER BY total_time DESC LIMIT 10;

-- 查看表统计
SELECT * FROM pg_stat_user_tables 
WHERE schemaname = 'public';

-- 查看索引使用情况
SELECT * FROM pg_stat_user_indexes;

-- 查看表膨胀
SELECT schemaname, tablename, 
       n_dead_tup, n_live_tup
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000;

-- VACUUM和ANALYZE
VACUUM ANALYZE users;
VACUUM FULL users;

🏗️ 实战案例

🛒 电商数据库设计

-- 用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    status VARCHAR(20) DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 商品表(使用JSONB存储规格)
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    description TEXT,
    price NUMERIC(10,2) NOT NULL,
    stock INTEGER DEFAULT 0,
    specs JSONB,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_specs ON products USING GIN (specs);

-- 订单表
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    total_amount NUMERIC(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_created_at ON orders(created_at);

📊 数据分析查询

-- 按月统计销售额(窗口函数)
SELECT 
    DATE_TRUNC('month', created_at) as month,
    SUM(total_amount) as monthly_sales,
    AVG(SUM(total_amount)) OVER (ORDER BY DATE_TRUNC('month', created_at)
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as moving_avg
FROM orders
WHERE created_at >= '2024-01-01'
GROUP BY DATE_TRUNC('month', created_at);

-- Top 10 用户(子查询)
SELECT u.username, o.total_spent
FROM (
    SELECT user_id, SUM(total_amount) as total_spent
    FROM orders
    GROUP BY user_id
    ORDER BY total_spent DESC
    LIMIT 10
) o
JOIN users u ON o.user_id = u.id;

-- 商品推荐(基于共同购买)
SELECT DISTINCT p2.id, p2.name
FROM order_items oi1
JOIN order_items oi2 ON oi1.order_id = oi2.order_id
JOIN products p1 ON oi1.product_id = p1.id
JOIN products p2 ON oi2.product_id = p2.id
WHERE p1.id = 123 AND p2.id != p1.id
LIMIT 10;

📍 PostGIS地理数据

-- 启用PostGIS扩展
CREATE EXTENSION postgis;

-- 创建地理位置表
CREATE TABLE places (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    location GEOGRAPHY(POINT, 4326)
);

CREATE INDEX idx_location ON places USING GIST (location);

-- 插入地理位置数据
INSERT INTO places (name, location) VALUES
('Beijing', 'POINT(116.4074 39.9042)'),
('Shanghai', 'POINT(121.4737 31.2304)');

-- 查找附近的地点
SELECT name, 
       ST_Distance(location, 'POINT(116.4074 39.9042)') as distance
FROM places
ORDER BY location <-> 'POINT(116.4074 39.9042)'
LIMIT 10;

-- 查找指定范围内的地点
SELECT name 
FROM places
WHERE ST_DWithin(
    location, 
    'POINT(116.4074 39.9042)'::geography, 
    50000  -- 50km
);

📋 PostgreSQL 最佳实践总结

✅ 设计原则

  • • 选择合适的索引类型
  • • 使用JSONB存储半结构化数据
  • • 合理设计分区策略
  • • 使用外键保证数据完整性
  • • 避免过度规范化
  • • 考虑读写比例

⚡ 性能优化

  • • 合理配置shared_buffers
  • • 定期VACUUM和ANALYZE
  • • 使用连接池减少开销
  • • 监控慢查询和表膨胀
  • • 使用部分索引优化
  • • 考虑读写分离

🔒 安全措施

  • • 使用强密码策略
  • • 限制数据库访问IP
  • • 使用SSL/TLS加密
  • • 定期备份和测试恢复
  • • 审计敏感操作
  • • 使用角色和权限控制

📊 运维监控

  • • 监控连接数和慢查询
  • • 跟踪数据库大小和表膨胀
  • • 定期检查索引使用率
  • • 监控WAL和复制延迟
  • • 建立告警机制
  • • 制定灾难恢复计划
← 返回数据库首页