跳转至

05-PostgreSQL 进阶

PostgreSQL 进阶

功能最强大的开源关系型数据库 目标:掌握 PostgreSQL 的高级特性,能在 AI 项目中灵活运用


📋 本章概览

预计学习时间: 3-4 小时 前置章节第 04 章: MySQL 实战 实践要求:完成 PostgreSQL 安装,实现 JSON 数据存储和全文搜索

本章内容: 1. PostgreSQL 简介与安装 2. PostgreSQL 特色功能 3. JSON/JSONB 数据类型 4. 全文搜索 5. 窗口函数 6. 高级索引 7. Python 操作 PostgreSQL


1. PostgreSQL 简介与安装

1.1 为什么选择 PostgreSQL

PostgreSQL vs MySQL

特性 PostgreSQL MySQL
SQL 标准兼容性 更严格 较宽松
扩展性 极强(支持自定义类型、函数) 有限
JSON 支持 JSONB (二进制,支持索引) JSON ( 5.7+为二进制存储,但索引能力弱于 PG )
全文搜索 内置强大功能 需额外配置
GIS 支持 PostGIS 业界标准 较弱
并发性能 MVCC 更优 良好
适用场景 复杂查询、 GIS 、 AI 项目 Web 应用、简单 CRUD

AI 项目选择 PostgreSQL 的理由: - JSONB 完美存储模型配置和实验元数据 - 向量扩展( pgvector )支持 AI 嵌入 - 复杂统计查询性能优秀 - 支持自定义函数和聚合

1.2 安装 PostgreSQL

Windows 安装

Bash
# 1. 下载安装程序
# https://www.postgresql.org/download/windows/

# 2. 使用Chocolatey
choco install postgresql

# 3. 默认端口5432,记住设置的密码

macOS 安装

Bash
# 使用Homebrew
brew install postgresql

# 启动服务
brew services start postgresql

# 创建数据库
createdb mydb

Linux ( Ubuntu )

Bash
# 安装
sudo apt update
sudo apt install postgresql postgresql-contrib

# 启动服务
sudo systemctl start postgresql

# 切换到postgres用户
sudo -u postgres psql

# 创建用户和数据库
CREATE USER myuser WITH PASSWORD 'password';
CREATE DATABASE mydb OWNER myuser;

Docker 安装

Bash
# 拉取镜像(2026年推荐PostgreSQL 17)
docker pull postgres:17

# 运行容器
docker run -d \
  --name postgres17 \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=your_password \
  -e POSTGRES_DB=mydb \
  -v postgres_data:/var/lib/postgresql/data \
  postgres:17

# 进入容器
docker exec -it postgres17 psql -U postgres -d mydb

1.3 基本操作

SQL
-- 连接数据库
psql -U username -d database_name -h localhost -p 5432

-- 查看版本
SELECT version();

-- 列出所有数据库
\l

-- 切换数据库
\c database_name

-- 列出所有表
\dt

-- 查看表结构
\d table_name

-- 查看所有用户
\du

-- 执行SQL文件
\i /path/to/script.sql

-- 退出
\q

2. PostgreSQL 数据类型

2.1 特色数据类型

SQL
-- 数组类型
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    scores INT[],                    -- 整数数组
    tags TEXT[]                     -- 字符串数组
);

-- 插入数组数据
INSERT INTO students (name, scores, tags)
VALUES ('张三', ARRAY[85, 90, 78], ARRAY['AI', 'Python']);

-- 数组查询
SELECT * FROM students WHERE 'AI' = ANY(tags);
SELECT * FROM students WHERE scores @> ARRAY[90];  -- 包含90分

-- 范围类型
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    duration TSRANGE                 -- 时间戳范围
);

INSERT INTO events (name, duration)
VALUES ('会议', '[2024-01-15 09:00, 2024-01-15 11:00)');

-- 查询重叠
SELECT * FROM events
WHERE duration && '[2024-01-15 10:00, 2024-01-15 12:00)';

-- UUID类型
CREATE TABLE api_keys (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    key_name VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 枚举类型
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'banned');

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50),
    status user_status DEFAULT 'active'
);

2.2 几何类型( GIS 基础)

SQL
-- 点
CREATE TABLE locations (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    coord POINT
);

INSERT INTO locations (name, coord)
VALUES ('北京', point(116.4074, 39.9042));

-- 查询距离
SELECT * FROM locations
WHERE coord <-> point(116.4074, 39.9042) < 1;

3. JSON/JSONB 数据类型

3.1 JSON vs JSONB

特性 JSON JSONB
存储格式 原始文本 二进制(解析后)
写入速度 稍慢
查询速度
支持索引
去重/排序
推荐使用 仅存储 查询频繁

3.2 JSONB 实战

SQL
-- 创建表
CREATE TABLE ml_experiments (
    exp_id SERIAL PRIMARY KEY,
    exp_name VARCHAR(100),
    config JSONB,                    -- 实验配置
    metrics JSONB,                   -- 实验指标
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入数据
INSERT INTO ml_experiments (exp_name, config, metrics) VALUES
('exp_001',
 '{"model": "ResNet50", "lr": 0.001, "batch_size": 32, "epochs": 100}',
 '{"train_acc": 0.95, "val_acc": 0.92, "test_acc": 0.91}'
),
('exp_002',
 '{"model": "VGG16", "lr": 0.0001, "batch_size": 64, "epochs": 150}',
 '{"train_acc": 0.93, "val_acc": 0.90, "test_acc": 0.89}'
);

-- 查询JSONB字段
-- 1. 提取字段
SELECT exp_name, config->>'model' as model FROM ml_experiments;

-- 2. 条件查询
SELECT * FROM ml_experiments
WHERE config->>'model' = 'ResNet50';

-- 3. 数值比较
SELECT * FROM ml_experiments
WHERE (config->>'lr')::float < 0.01;

-- 4. 嵌套查询
SELECT * FROM ml_experiments
WHERE metrics->>'val_acc' > '0.91';

-- 5. 检查键是否存在
SELECT * FROM ml_experiments
WHERE config ? 'optimizer';  -- 是否有optimizer键

-- 6. 检查多个键
SELECT * FROM ml_experiments
WHERE config ?& ARRAY['model', 'lr'];  -- 同时有这两个键

-- 7. 数组元素查询
SELECT * FROM ml_experiments
WHERE config->'layers' @> '["conv1", "conv2"]';

-- 更新JSONB
-- 1. 更新整个字段
UPDATE ml_experiments
SET config = '{"model": "ResNet101", "lr": 0.001}'
WHERE exp_id = 1;

-- 2. 更新特定键
UPDATE ml_experiments
SET config = jsonb_set(config, '{lr}', '0.0005')
WHERE exp_id = 1;

-- 3. 添加新键
UPDATE ml_experiments
SET config = config || '{"optimizer": "Adam"}'
WHERE exp_id = 1;

-- 4. 删除键
UPDATE ml_experiments
SET config = config - 'optimizer'
WHERE exp_id = 1;

3.3 JSONB 索引

SQL
-- GIN索引(通用倒排索引)
CREATE INDEX idx_config_gin ON ml_experiments USING GIN (config);

-- 特定路径的GIN索引
CREATE INDEX idx_config_model ON ml_experiments USING GIN ((config->'model'));

-- 表达式索引(数值字段)
CREATE INDEX idx_config_lr ON ml_experiments (((config->>'lr')::float));

-- 查询使用索引
EXPLAIN ANALYZE  -- EXPLAIN查看查询执行计划
SELECT * FROM ml_experiments
WHERE config @> '{"model": "ResNet50"}';

4. 全文搜索

4.1 基础全文搜索

SQL
-- 创建表
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200),
    content TEXT,
    search_vector TSVECTOR
);

-- 插入数据
INSERT INTO articles (title, content) VALUES
('PostgreSQL入门教程', 'PostgreSQL是一个强大的开源关系型数据库...'),
('Python机器学习', '使用Python进行机器学习开发,需要掌握NumPy、Pandas...'),
('深度学习框架对比', 'TensorFlow和PyTorch是目前最流行的深度学习框架...');

-- 创建搜索向量(中文需要额外配置)
UPDATE articles
SET search_vector = to_tsvector('english', title || ' ' || content);

-- 简单搜索
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('english', 'Python');

-- 多词搜索
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('english', 'machine & learning');

-- 或搜索
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('english', 'TensorFlow | PyTorch');

-- 短语搜索
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('english', 'deep <-> learning');

-- 带排名的搜索
SELECT
    id,
    title,
    ts_rank(search_vector, to_tsquery('english', 'learning')) AS rank
FROM articles
WHERE search_vector @@ to_tsquery('english', 'learning')
ORDER BY rank DESC;

4.2 自动更新搜索向量

SQL
-- 创建函数
CREATE OR REPLACE FUNCTION update_search_vector()
RETURNS TRIGGER AS $$
BEGIN  -- 事务保证操作原子性
    NEW.search_vector := to_tsvector('english', NEW.title || ' ' || NEW.content);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 创建触发器
CREATE TRIGGER trigger_update_search_vector
BEFORE INSERT OR UPDATE ON articles
FOR EACH ROW
EXECUTE FUNCTION update_search_vector();

-- 现在插入数据会自动更新search_vector
INSERT INTO articles (title, content)
VALUES ('新文章', '这是新文章的内容...');

4.3 中文全文搜索

SQL
-- 安装zhparser(需要编译安装)
-- CREATE EXTENSION zhparser;
-- CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);

-- 或使用pg_jieba
-- 更简单的方法是使用ngram

-- 使用ngram进行中文搜索
CREATE TEXT SEARCH CONFIGURATION chinese_ngram (COPY = simple);

ALTER TEXT SEARCH CONFIGURATION chinese_ngram
ALTER MAPPING FOR asciiword, word WITH simple;

-- 创建ngram索引
CREATE INDEX idx_articles_search ON articles
USING GIN (search_vector);

-- 实际项目中建议使用ElasticSearch或专门的中文搜索方案

5. 窗口函数

5.1 窗口函数基础

SQL
-- 示例数据
CREATE TABLE sales (
    id SERIAL PRIMARY KEY,
    product VARCHAR(50),
    category VARCHAR(50),
    amount DECIMAL(10,2),
    sale_date DATE
);

INSERT INTO sales (product, category, amount, sale_date) VALUES
('iPhone', '手机', 5999.00, '2024-01-01'),
('iPad', '平板', 3999.00, '2024-01-01'),
('MacBook', '电脑', 12999.00, '2024-01-02'),
('iPhone', '手机', 5999.00, '2024-01-03'),
('AirPods', '配件', 1999.00, '2024-01-03'),
('iPad', '平板', 3999.00, '2024-01-04');

-- 1. ROW_NUMBER() - 行号
SELECT
    product,
    amount,
    ROW_NUMBER() OVER (ORDER BY amount DESC) AS rank
FROM sales;

-- 2. RANK() - 排名(允许并列)
SELECT
    product,
    amount,
    RANK() OVER (ORDER BY amount DESC) AS rank
FROM sales;

-- 3. DENSE_RANK() - 密集排名
SELECT
    product,
    amount,
    DENSE_RANK() OVER (ORDER BY amount DESC) AS rank
FROM sales;

-- 4. 分组窗口函数
SELECT
    product,
    category,
    amount,
    ROW_NUMBER() OVER (PARTITION BY category ORDER BY amount DESC) AS category_rank
FROM sales;

-- 5. 累计求和
SELECT
    product,
    amount,
    SUM(amount) OVER (ORDER BY sale_date) AS cumulative_sum
FROM sales;

-- 6. 分组累计
SELECT
    product,
    category,
    amount,
    SUM(amount) OVER (PARTITION BY category ORDER BY sale_date) AS category_cumulative
FROM sales;

-- 7. 移动平均
SELECT
    product,
    amount,
    AVG(amount) OVER (ORDER BY sale_date ROWS 2 PRECEDING) AS moving_avg
FROM sales;

-- 8. LAG/LEAD - 前后行
SELECT
    product,
    amount,
    LAG(amount, 1) OVER (ORDER BY sale_date) AS prev_amount,
    LEAD(amount, 1) OVER (ORDER BY sale_date) AS next_amount
FROM sales;

-- 9. FIRST_VALUE/LAST_VALUE
SELECT
    product,
    amount,
    FIRST_VALUE(amount) OVER (ORDER BY amount) AS min_amount,
    LAST_VALUE(amount) OVER (ORDER BY amount
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS max_amount
FROM sales;

-- 10. NTILE - 分桶
SELECT
    product,
    amount,
    NTILE(4) OVER (ORDER BY amount) AS quartile
FROM sales;

5.2 AI 场景窗口函数应用

SQL
-- 实验指标趋势分析
CREATE TABLE experiment_metrics (
    exp_id INT,
    step INT,
    loss DECIMAL(10,6),
    accuracy DECIMAL(5,4)
);

-- 计算滑动平均(平滑曲线)
SELECT
    exp_id,
    step,
    loss,
    AVG(loss) OVER (  -- 窗口函数:在结果集上滑动计算
        PARTITION BY exp_id
        ORDER BY step
        ROWS BETWEEN 10 PRECEDING AND CURRENT ROW
    ) AS smoothed_loss
FROM experiment_metrics;

-- 计算改进幅度
SELECT
    exp_id,
    step,
    accuracy,
    accuracy - LAG(accuracy, 10) OVER (PARTITION BY exp_id ORDER BY step) AS improvement
FROM experiment_metrics;

6. 高级索引

6.1 索引类型

SQL
-- B-tree索引(默认)
CREATE INDEX idx_name ON users(name);

-- Hash索引(等值查询)
CREATE INDEX idx_hash ON users USING HASH (name);

-- GiST索引(地理数据、范围)
CREATE INDEX idx_coord ON locations USING GiST (coord);

-- GIN索引(数组、JSONB、全文搜索)
CREATE INDEX idx_tags ON articles USING GIN (tags);
CREATE INDEX idx_config ON ml_experiments USING GIN (config);

-- BRIN索引(大块数据,时序)
CREATE INDEX idx_time 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(name, age);

-- 唯一索引
CREATE UNIQUE INDEX idx_unique_email ON users(email);

6.2 索引优化建议

SQL
-- 1. 分析表
ANALYZE users;

-- 2. 查看执行计划
EXPLAIN ANALYZE SELECT * FROM users WHERE name = '张三';

-- 3. 查看索引使用情况
SELECT
    schemaname,
    tablename,
    indexname,
    idx_scan,
    idx_tup_read
FROM pg_stat_user_indexes
WHERE tablename = 'users';

-- 4. 清理未使用的索引
-- 先监控一段时间,然后删除idx_scan为0的索引

7. Python 操作 PostgreSQL

7.1 使用 psycopg2

安装

Bash
pip install psycopg2-binary

基础操作

Python
import psycopg2
from psycopg2.extras import RealDictCursor, Json

# 连接数据库
conn = psycopg2.connect(
    host='localhost',
    port=5432,
    database='mydb',
    user='postgres',
    password='your_password'
)

# 创建表
cursor = conn.cursor()
cursor.execute("""
    CREATE TABLE IF NOT EXISTS experiments (
        exp_id SERIAL PRIMARY KEY,
        exp_name VARCHAR(100),
        config JSONB,
        metrics JSONB,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
""")
conn.commit()

# 插入JSONB数据
config = {"model": "ResNet50", "lr": 0.001, "batch_size": 32}
metrics = {"train_acc": 0.95, "val_acc": 0.92}

cursor.execute(
    "INSERT INTO experiments (exp_name, config, metrics) VALUES (%s, %s, %s)",
    ('exp_001', Json(config), Json(metrics))
)
conn.commit()

# 查询数据
cursor.execute("SELECT * FROM experiments WHERE config->>'model' = 'ResNet50'")
rows = cursor.fetchall()
for row in rows:
    print(row)

# 使用字典游标
cursor = conn.cursor(cursor_factory=RealDictCursor)
cursor.execute("SELECT * FROM experiments")
rows = cursor.fetchall()
for row in rows:
    print(dict(row))

cursor.close()
conn.close()

7.2 使用 SQLAlchemy

Python
from sqlalchemy import create_engine, Column, Integer, String, DateTime, JSON
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy.dialects.postgresql import JSONB
from datetime import datetime

# 创建引擎
engine = create_engine(
    'postgresql+psycopg2://postgres:password@localhost:5432/mydb',
    echo=True
)

Base = declarative_base()

# 定义模型
class Experiment(Base):
    __tablename__ = 'experiments'

    exp_id = Column(Integer, primary_key=True)
    exp_name = Column(String(100))
    config = Column(JSONB)  # 使用JSONB类型
    metrics = Column(JSONB)
    created_at = Column(DateTime, default=datetime.now)

# 创建表
Base.metadata.create_all(engine)

# 创建会话
Session = sessionmaker(bind=engine)
session = Session()

# 插入数据
exp = Experiment(
    exp_name='exp_002',
    config={'model': 'VGG16', 'lr': 0.0001},
    metrics={'accuracy': 0.93}
)
session.add(exp)
session.commit()

# 查询JSONB字段
results = session.query(Experiment).filter(
    Experiment.config['model'].astext == 'VGG16'
).all()

for r in results:
    print(f"{r.exp_name}: {r.config}")

session.close()

7.3 使用连接池

Python
from psycopg2 import pool

# 创建连接池
cp = pool.ThreadedConnectionPool(
    minconn=1,
    maxconn=10,
    host='localhost',
    port=5432,
    database='mydb',
    user='postgres',
    password='your_password'
)

# 获取连接
conn = cp.getconn()
try:  # try/except捕获异常
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM experiments")
    rows = cursor.fetchall()
    print(rows)
finally:
    # 归还连接
    cp.putconn(conn)

8. PostgreSQL 扩展

8.1 常用扩展

SQL
-- 查看可用扩展
SELECT * FROM pg_available_extensions;

-- 查看已安装扩展
SELECT * FROM pg_extension;

-- 安装扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- 生成UUID
SELECT uuid_generate_v4();

-- 密码哈希
SELECT crypt('password', gen_salt('bf'));

-- 验证密码
SELECT crypt('password', stored_hash) = stored_hash;

8.2 pgvector (向量数据库)

SQL
-- 安装pgvector
CREATE EXTENSION vector;

-- 创建表
CREATE TABLE items (
    id SERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(1536)  -- OpenAI嵌入维度
);

-- 插入向量
INSERT INTO items (content, embedding) VALUES
('示例文本', '[0.1, 0.2, 0.3, ...]');  -- 1536维向量

-- 相似度搜索(余弦相似度)
SELECT *, embedding <=> '[0.1, 0.2, ...]' AS distance
FROM items
ORDER BY embedding <=> '[0.1, 0.2, ...]'
LIMIT 5;

-- 创建IVFFlat索引
CREATE INDEX idx_embedding ON items  -- INDEX索引加速查询
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);  -- CTE公共表表达式:临时命名结果集

🎯 本章自测

基础练习

  1. 安装 PostgreSQL ,创建数据库和表

  2. 使用 JSONB 存储 AI 实验配置,实现以下查询

  3. 查询使用特定模型的实验
  4. 查询学习率在 0.001 到 0.01 之间的实验
  5. 统计每个模型的实验数量

  6. 实现全文搜索功能

  7. 创建文章表,包含标题和内容
  8. 实现自动更新的搜索向量
  9. 实现带排名的搜索

进阶练习

  1. 使用窗口函数分析销售数据
  2. 计算每个产品的销售排名
  3. 计算累计销售额
  4. 计算同比/环比增长

  5. 设计一个 AI 实验管理系统的数据库

  6. 使用 JSONB 存储灵活的配置
  7. 使用数组存储标签
  8. 使用 UUID 作为主键

Python 集成

  1. 使用 Python 连接 PostgreSQL ,实现
  2. 实验数据的 CRUD 操作
  3. JSONB 字段的查询和更新
  4. 批量数据导入

📚 扩展阅读

推荐资源

下一步

完成本章后,继续学习 第 06 章: NoSQL 数据库,了解非关系型数据库的世界!


本章完 | 预计学习时间: 3-4 小时