Python数据库编程实战:从psycopg3到SQLAlchemy Core — PostgreSQL篇

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() 取下一行 tupleNone
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 上建立数据库服务需:

  1. 注册 supabase.com 账号
  2. 创建项目 → 获得连接字符串
  3. 用上面学到的 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) 实战编写,所有输出均为服务器端真实执行结果。