05-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
# 拉取镜像
docker pull postgres:15
# 运行容器
docker run -d \
--name postgres15 \
-p 5432:5432 \
-e POSTGRES_PASSWORD=your_password \
-e POSTGRES_DB=mydb \
-v postgres_data:/var/lib/postgresql/data \
postgres:15
# 进入容器
docker exec -it postgres15 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¶
安装:
基础操作:
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公共表表达式:临时命名结果集
🎯 本章自测¶
基础练习¶
-
安装PostgreSQL,创建数据库和表
-
使用JSONB存储AI实验配置,实现以下查询:
- 查询使用特定模型的实验
- 查询学习率在0.001到0.01之间的实验
-
统计每个模型的实验数量
-
实现全文搜索功能:
- 创建文章表,包含标题和内容
- 实现自动更新的搜索向量
- 实现带排名的搜索
进阶练习¶
- 使用窗口函数分析销售数据:
- 计算每个产品的销售排名
- 计算累计销售额
-
计算同比/环比增长
-
设计一个AI实验管理系统的数据库:
- 使用JSONB存储灵活的配置
- 使用数组存储标签
- 使用UUID作为主键
Python集成¶
- 使用Python连接PostgreSQL,实现:
- 实验数据的CRUD操作
- JSONB字段的查询和更新
- 批量数据导入
📚 扩展阅读¶
推荐资源¶
下一步¶
完成本章后,继续学习 第06章:NoSQL数据库,了解非关系型数据库的世界!
本章完 | 预计学习时间:3-4小时