Python数据库编程实战:从psycopg3到SQLAlchemy Core --- PostgreSQL篇
华为云 FlexusX (8vCPU/16GiB) · Ubuntu 24.04.4 · Python 3.12.3 · PostgreSQL 16.14
psycopg 3.3.4 · SQLAlchemy 2.0.51 · 全真实输出 · 零理论空谈
目录
- 环境架构
- [第一章:psycopg3 --- PostgreSQL原生驱动](#第一章:psycopg3 — PostgreSQL原生驱动)
- [1.1 环境搭建与连接](#1.1 环境搭建与连接)
- [1.2 DDL建表与DML写入](#1.2 DDL建表与DML写入)
- [1.3 SELECT查询与游标](#1.3 SELECT查询与游标)
- [1.4 事务控制:COMMIT/ROLLBACK](#1.4 事务控制:COMMIT/ROLLBACK)
- [1.5 参数化查询与SQL注入防护](#1.5 参数化查询与SQL注入防护)
- [第二章:SQLAlchemy Core --- 数据库抽象层](#第二章:SQLAlchemy Core — 数据库抽象层)
- [2.1 引擎与元数据](#2.1 引擎与元数据)
- [2.2 Core层CRUD操作](#2.2 Core层CRUD操作)
- 第三章:Supabase云数据库
- [psycopg3 vs SQLAlchemy 终极对比](#psycopg3 vs SQLAlchemy 终极对比)
- 踩坑记录
环境架构
┌──────────────────────────────────────────────────────┐
│ ecs-88e7-0001 │
│ 139.9.128.210 (华为云香港) │
│ │
│ ┌─────────────┐ ┌─────────────────────────────┐ │
│ │ Python 3.12 │────▶│ PostgreSQL 16.14 │ │
│ │ │ │ localhost:5432 │ │
│ │ psycopg 3.3 │ │ DB: python_db │ │
│ │ SQLAlchemy │ │ User: python_user │ │
│ │ 2.0.51 │ │ │ │
│ └─────────────┘ └─────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
数据库准备(执行过的命令):
bash
apt-get install -y postgresql postgresql-client
su - postgres -c "psql -c \"CREATE DATABASE python_db;\""
su - postgres -c "psql -c \"CREATE USER python_user WITH PASSWORD 'Python@123';\""
su - postgres -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE python_db TO python_user;\""
su - postgres -c "psql -d python_db -c 'GRANT ALL ON SCHEMA public TO python_user;'"
pip3 install 'psycopg[binary]' sqlalchemy --break-system-packages
第一章:psycopg3 --- PostgreSQL原生驱动
1.1 环境搭建与连接
psycopg3 是 PostgreSQL 官方推荐的 Python 驱动(psycopg2 的继任者),3.x 版本完全重写,支持 async/await。
python
import psycopg
import sys
print(f"psycopg 版本: {psycopg.__version__}") # 3.3.4
print(f"Python 版本: {sys.version.split()[0]}") # 3.12.3
# 连接字符串 (conninfo)
DB = "host=localhost dbname=python_db user=python_user password=Python@123"
conn = psycopg.connect(DB)
真实输出:
连接成功!
数据库: python_db
用户: python_user
主机: localhost
端口: 5432
服务端版本: 160014
事务状态: 0 (空闲)
连接字符串格式(conninfo)逐字段解释:
| 参数 | 含义 | 示例 |
|---|---|---|
host |
数据库主机地址 | localhost / 192.168.0.1 |
dbname |
数据库名 | python_db |
user |
连接用户 | python_user |
password |
连接密码 | Python@123 |
port |
端口(默认5432) | 5432 |
也可以通过 psycopg.connect(host=..., dbname=..., user=..., password=...) 关键字传参。
1.2 DDL建表与DML写入
python
with conn.cursor() as cur:
# DDL: CREATE TABLE
cur.execute("""
CREATE TABLE IF NOT EXISTS books (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author VARCHAR(100) NOT NULL,
price NUMERIC(10, 2) DEFAULT 0,
pages INTEGER,
publisher VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
print("CREATE TABLE books --- 成功")
# DML: INSERT
with conn.cursor() as cur:
cur.execute("DELETE FROM books") # 清空, 避免重复
books_data = [
("Python编程:从入门到实践", "Eric Matthes", 89.00, 459, "人民邮电出版社"),
("流畅的Python", "Luciano Ramalho", 139.00, 700, "人民邮电出版社"),
("利用Python进行数据分析", "Wes McKinney", 109.00, 502, "机械工业出版社"),
("Python Cookbook", "David Beazley", 129.00, 687, "人民邮电出版社"),
("Effective Python", "Brett Slatkin", 99.00, 256, "机械工业出版社"),
]
for book in books_data:
cur.execute(
"INSERT INTO books (title, author, price, pages, publisher) VALUES (%s, %s, %s, %s, %s)",
book
)
print(f"INSERT INTO books --- 成功插入 {len(books_data)} 条记录")
conn.commit() # 显式提交事务!
关键知识点:
SERIAL= PostgreSQL 自增整数(等价INTEGER GENERATED ALWAYS AS IDENTITY)NUMERIC(10, 2)= 定点小数,共10位,小数2位(适合金额)- 占位符使用
%s(不是?也不是%d也不是:name) conn.commit()显式提交,否则自动回滚
1.3 SELECT查询与游标
python
with conn.cursor() as cur:
# 计数
cur.execute("SELECT count(*) FROM books")
count = cur.fetchone()[0]
print(f"总记录数: {count}")
# 全表查询
cur.execute("SELECT id, title, author, price, pages FROM books ORDER BY id")
rows = cur.fetchall()
print(f"{'ID':<4} {'书名':<30} {'作者':<22} {'价格':>8} {'页数':>6}")
print("-" * 75)
for row in rows:
print(f"{row[0]:<4} {row[1]:<30} {row[2]:<22} {row[3]:>7.2f} {row[4]:>6}")
真实输出:
总记录数: 5
ID 书名 作者 价格 页数
---------------------------------------------------------------------------
1 Python编程:从入门到实践 Eric Matthes 89.00 459
2 流畅的Python Luciano Ramalho 139.00 700
3 利用Python进行数据分析 Wes McKinney 109.00 502
4 Python Cookbook David Beazley 129.00 687
5 Effective Python Brett Slatkin 99.00 256
聚合查询:
python
cur.execute("SELECT min(price), max(price), avg(price), sum(price) FROM books")
mn, mx, avg, total = cur.fetchone()
print(f"MIN={mn:.2f} MAX={mx:.2f} AVG={avg:.2f} SUM={total:.2f}")
cur.execute("""
SELECT publisher, count(*), avg(price)
FROM books
GROUP BY publisher
ORDER BY count(*) DESC
""")
真实输出:
聚合: MIN=89.00 MAX=139.00 AVG=113.00 SUM=565.00
按出版社分组:
出版社 数量 均价
------------------------------------
人民邮电出版社 3 119.00
机械工业出版社 2 104.00
游标方法速查:
| 方法 | 含义 | 返回类型 |
|---|---|---|
fetchone() |
取下一行 | tuple 或 None |
fetchall() |
取全部剩余行 | list[tuple] |
fetchmany(n) |
取下n行 | list[tuple] |
rowcount |
受影响行数(DML) | int |
批量插入:executemany:
python
new_books = [
("重构:改善既有代码的设计", "Martin Fowler", 99.00, 428, "人民邮电出版社"),
("代码整洁之道", "Robert C. Martin", 59.00, 422, "人民邮电出版社"),
]
cur.executemany(
"INSERT INTO books (title, author, price, pages, publisher) VALUES (%s, %s, %s, %s, %s)",
new_books
)
# executemany --- 批量插入 2 条
1.4 事务控制:COMMIT/ROLLBACK
PostgreSQL 默认每条 SQL 在事务中执行。调用 conn.commit() 确认,conn.rollback() 撤销。
python
# 插入但不提交
with conn.cursor() as cur:
cur.execute("INSERT INTO books (...) VALUES (...)")
cur.execute("SELECT count(*) FROM books")
count_before = cur.fetchone()[0]
print(f"插入后记录数: {count_before}") # 8
# 回滚!
conn.rollback()
print("ROLLBACK --- 事务已回滚")
# 验证
with conn.cursor() as cur:
cur.execute("SELECT count(*) FROM books")
count_after = cur.fetchone()[0]
print(f"回滚后记录数: {count_after}") # 7 (撤销了刚刚的INSERT)
# ✓ ROLLBACK 验证通过 --- 未提交的插入已被撤销
事务状态枚举(psycopg 内置):
| 状态常量 | 含义 |
|---|---|
TransactionStatus.IDLE |
空闲(不在事务中) |
TransactionStatus.ACTIVE |
有活动命令 |
TransactionStatus.INTRANS |
事务中 |
TransactionStatus.INERROR |
事务出错(需ROLLBACK) |
TransactionStatus.UNKNOWN |
未知 |
1.5 参数化查询与SQL注入防护
这是数据库编程最重要的安全问题,两种安全方式 + 一种危险方式:
python
# ✅ 方式1: %s 占位符(位置参数)
search_author = "Martin"
cur.execute(
"SELECT title FROM books WHERE author LIKE %s",
(f"%{search_author}%",) # 元组! 单元素也要逗号
)
# ✅ 方式2: 命名参数(PyFormat,推荐复杂查询)
cur.execute(
"SELECT title, price FROM books WHERE price BETWEEN %(lo)s AND %(hi)s",
{"lo": 80, "hi": 120}
)
SQL注入演示:
python
malicious_input = "'; DROP TABLE books; --"
# ❌ 危险! 字符串拼接 ------ 永远不要这样做!
# sql = f"SELECT * FROM books WHERE title = '{malicious_input}'"
# 实际SQL: SELECT * FROM books WHERE title = ''; DROP TABLE books; --'
# 后果: 整张表被删除!
# ✅ 安全: 参数化查询
cur.execute("SELECT * FROM books WHERE title = %s", (malicious_input,))
result = cur.fetchone()
# ✓ 恶意输入被安全转义为字面字符串, 查询无结果
防注入铁律:
| ❌ 永远不要 | ✅ 始终使用 |
|---|---|
f"SELECT * FROM t WHERE col = '{user_input}'" |
execute("...WHERE col = %s", (user_input,)) |
"...WHERE col = " + user_input |
execute("...WHERE col = %(val)s", {"val": user_input}) |
"...WHERE col = %s" % user_input |
execute("...WHERE col = %s", params) |
第二章:SQLAlchemy Core --- 数据库抽象层
SQLAlchemy 分两层:
- Core 层(本文重点):SQL表达式构建,不用ORM映射类
- ORM 层:对象关系映射,class -> table 对映
2.1 引擎与元数据
python
from sqlalchemy import (
create_engine, MetaData, Table, Column, Integer,
String, Numeric, DateTime, text, select, insert,
update, delete, func, desc
)
import datetime
# 创建引擎
DB_URL = "postgresql+psycopg://python_user:Python%40123@localhost:5432/python_db"
engine = create_engine(DB_URL, echo=False) # echo=True 可看SQL日志
print(f"引擎创建成功!")
print(f" PostgreSQL 版本: {pg_ver}")
print(f" 当前数据库: python_db, 当前用户: python_user")
踩坑 :密码含
@等特殊字符时,URL中需转义@→%40,否则被误解析为user@host结构。
表定义(不使用ORM类):
python
metadata = MetaData()
students = Table(
"students", metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("name", String(50), nullable=False),
Column("age", Integer, nullable=False),
Column("grade", String(20)),
Column("score", Numeric(5, 2)),
Column("enrolled", DateTime, default=datetime.datetime.now),
)
# 在数据库中创建表
metadata.create_all(engine)
print("CREATE TABLE students --- 成功")
列类型对照:
| SQLAlchemy | PostgreSQL | Python |
|---|---|---|
Integer |
INTEGER |
int |
String(n) |
VARCHAR(n) |
str |
Numeric(p, s) |
NUMERIC(p, s) |
Decimal |
DateTime |
TIMESTAMP |
datetime |
Boolean |
BOOLEAN |
bool |
Text |
TEXT |
str |
2.2 Core层CRUD操作
INSERT --- 单条和批量:
python
# 单条
stmt = insert(students).values(
name="张三", age=20, grade="A", score=88.5,
enrolled=datetime.datetime(2024, 9, 1)
)
conn.execute(stmt)
# 批量 (executemany风格)
stmt = insert(students)
conn.execute(stmt, [
{"name": "李四", "age": 21, "grade": "A", "score": 92.0},
{"name": "王五", "age": 19, "grade": "B", "score": 75.5},
{"name": "赵六", "age": 22, "grade": "A", "score": 95.0},
{"name": "孙七", "age": 20, "grade": "C", "score": 60.0},
{"name": "周八", "age": 21, "grade": "B", "score": 78.0},
])
conn.commit()
SELECT --- 条件/排序/聚合:
python
# 全表 + 排序
stmt = select(students).order_by(desc(students.c.score))
result = conn.execute(stmt)
真实输出:
姓名 年龄 等级 成绩
------------------------------
赵六 22 A 95.0
李四 21 A 92.0
张三 20 A 88.5
周八 21 B 78.0
王五 19 B 75.5
孙七 20 C 60.0
WHERE条件:
python
stmt = select(students).where(students.c.score >= 80)
# 成绩>=80: 3人 (张三88.5 / 李四92.0 / 赵六95.0)
聚合 + 分组:
python
# 聚合
stmt = select(
func.count(students.c.id).label("人数"),
func.avg(students.c.score).label("平均分"),
func.max(students.c.score).label("最高分"),
func.min(students.c.score).label("最低分"),
)
# 人数=6 平均分=81.5 最高分=95.00 最低分=60.00
# GROUP BY
stmt = select(
students.c.grade,
func.count(students.c.id).label("cnt"),
func.avg(students.c.score).label("avg_s")
).group_by(students.c.grade).order_by(students.c.grade)
# A级 --- 3人 平均分91.8
# B级 --- 2人 平均分76.8
# C级 --- 1人 平均分60.0
UPDATE:
python
stmt = update(students).where(
students.c.name == "孙七"
).values(score=68.0, grade="B")
conn.execute(stmt)
conn.commit()
# UPDATE --- 孙七成绩从60→68, 等级C→B
DELETE:
python
stmt = delete(students).where(students.c.score < 70)
conn.execute(stmt)
conn.commit()
# DELETE --- 删除成绩<70的学生,剩余5人
最终学生表(DELETE后):
ID 姓名 年龄 等级 成绩
-----------------------------------
1 张三 20 A 88.5
2 李四 21 A 92.0
3 王五 19 B 75.5
4 赵六 22 A 95.0
6 周八 21 B 78.0
注意:id=5(孙七)已被DELETE移除。
清理:
python
metadata.drop_all(engine)
# DROP TABLE students --- 清理完成
第三章:Supabase云数据库
Supabase 是 "开源 Firebase 替代品",提供托管的 PostgreSQL 服务。
本节为参考性内容。实际在 Supabase 上建立数据库服务需:
- 注册 supabase.com 账号
- 创建项目 → 获得连接字符串
- 用上面学到的 psycopg3 或 SQLAlchemy 直连
python
# Supabase 连接示例
import psycopg
# Supabase 会提供类似这样的连接字符串
SUPABASE_URL = "db.xxxxx.supabase.co"
SUPABASE_KEY = "your-service-role-key"
conn = psycopg.connect(
host=SUPABASE_URL,
port=5432,
dbname="postgres",
user="postgres",
password=SUPABASE_KEY,
sslmode="require" # Supabase 要求 SSL
)
psycopg3 vs SQLAlchemy 终极对比
┌──────────────────────────────────────────────────────────────┐
│ 选型决策树 │
│ │
│ 需要 async/await? ─── Yes ──▶ psycopg3 (原生支持) │
│ │ │
│ No │
│ │ │
│ 需要切换数据库(MySQL→PG)? ── Yes ──▶ SQLAlchemy Core │
│ │ │
│ No │
│ │ │
│ 简单CRUD / 数据分析? ── Yes ──▶ psycopg3 (更轻量) │
│ │ │
│ No │
│ │ │
│ 复杂业务逻辑 / ORM? ── Yes ──▶ SQLAlchemy ORM │
└──────────────────────────────────────────────────────────────┘
| 维度 | psycopg3 | SQLAlchemy Core |
|---|---|---|
| 定位 | PostgreSQL原生驱动 | 通用数据库抽象层 |
| 版本 | 3.3.4 | 2.0.51 |
| async | ✅ 原生 AsyncConnection |
✅ 2.0+ 原生支持 |
| 数据库无关 | ❌ 仅PostgreSQL | ✅ 通过dialect换库 |
| SQL写法 | 手写SQL字符串 | Python表达式构建 |
| 防止注入 | %s 占位符 |
自动参数化 |
| 性能 | 最接近裸SQL | 略有抽象开销 |
| 学习曲线 | 低(会SQL即可) | 中(需学习表达式API) |
| 适用场景 | 脚本/ETL/数据分析 | 复杂业务应用 |
| 安装 | pip install psycopg[binary] |
pip install sqlalchemy |
SQL写法对比:
python
# psycopg3:手写SQL
cur.execute("SELECT * FROM books WHERE price > %s", (100,))
# SQLAlchemy Core:Python表达式
stmt = select(books).where(books.c.price > 100)
conn.execute(stmt)
# 两者生成完全相同的SQL,结果也相同
踩坑记录
| # | 问题 | 现象 | 修复 |
|---|---|---|---|
| 1 | 密码含 @ → URL解析错误 |
failed to resolve host '123@localhost' |
URL转义:@ → %40 |
| 2 | PEP 668 pip限制 | externally-managed-environment |
--break-system-packages |
| 3 | python3-psycopg2 vs psycopg |
apt install python3-psycopg2 装的是 v2 |
用 pip install psycopg[binary] 装 v3 |
| 4 | 单参数元组忘记逗号 | execute(sql, (val)) → execute(sql, (val,)) |
单元素元组必须 (val,) |
| 5 | 忘记commit | INSERT后 SELECT count(*) 看不到新数据 |
始终显式 conn.commit() |
| 6 | PyPI超时(华为云香港) | ConnectTimeout → 阿里云镜像 |
-i https://mirrors.aliyun.com/pypi/simple/ |
总结
本文在华为云 FlexusX (8vCPU/16GiB) 服务器上,从零搭建 PostgreSQL 16.14,通过 9 个实验完整覆盖了 Python 数据库编程两大核心:
| 模块 | 实验数 | 核心收获 |
|---|---|---|
| psycopg3 | 7 | 连接/DDL/DML/SELECT/聚合/GROUP BY/事务/防注入 |
| SQLAlchemy Core | 2 | 引擎/元数据/Table定义/表达式CRUD/聚合分组 |
一句话建议:简单脚本用 psycopg3(直接写SQL最灵活),多表关联业务用 SQLAlchemy Core(Python表达式更安全、可读),需要复杂对象模型时上 SQLAlchemy ORM。
本文基于华为云 ecs-88e7-0001 (139.9.128.210) 实战编写,所有输出均为服务器端真实执行结果。