SQLAlchemy
SQLAlchemy 是 Python 下成熟的 ORM 与 SQL 工具库,支持多种数据库方言,换库(如从 SQLite 到 PostgreSQL)时几乎不用改业务代码。详见 SQLAlchemy 2.0 官方文档。
安装与引擎
pip install sqlalchemy
# 使用 PostgreSQL 时通常还需:pip install psycopg2-binary
所有连接都从 Engine 开始。Engine 是连接池与数据库 URL 的入口,应用内通常全局只建一个:
from sqlalchemy import create_engine
engine = create_engine("sqlite:///:memory:", echo=True)
# PostgreSQL: "postgresql+psycopg2://user:pass@host:5432/dbname"
# MySQL: "mysql+pymysql://user:pass@host:3306/dbname"
- URL 含义:
方言+驱动://用户:密码@主机:端口/库名。SQLite 无服务端,用/:memory:表示内存库。 - echo=True:把发出的 SQL 打印到标准输出,调试用;生产应关闭。
- 连接池:默认每个 Engine 自带连接池(如 QueuePool),无需手写连接管理;高并发时可调
pool_size、max_overflow。
Engine 不持有“当前连接”;占用连接的是 engine.connect() 或通过 Session 发起的操作。请求级、短生命周期的 Session 配全局单例 Engine 即可。
基本运行示例
下面这段可在本机直接运行(内存 SQLite,无需装数据库)。后文中的 engine、Base、User 均沿用此处定义。
from sqlalchemy import create_engine, select, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
engine = create_engine("sqlite:///:memory:", echo=False)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30), nullable=False)
email: Mapped[str] = mapped_column(String(255), nullable=False)
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(User(name="alice", email="alice@example.com"))
session.add(User(name="bob", email="bob@example.com"))
session.commit()
with Session(engine) as session:
stmt = select(User).where(User.name == "alice")
user = session.scalars(stmt).first()
print(user.name, user.email) # alice alice@example.com
类型对应与转换
ORM 把 Python 类型 映射到 数据库列类型,读写时做序列化/反序列化。对应关系搞错会报错或产生静默错误。
类型对应表
| Python 类型(业务层) | SQLAlchemy 类型(列定义) | 常见数据库列类型 |
|---|---|---|
int | Integer / BigInteger | INTEGER, BIGINT |
float | Float / Double | REAL, DOUBLE |
str | String(length) / Text | VARCHAR(n), TEXT |
bool | Boolean | BOOLEAN(部分数据库用 SMALLINT) |
datetime.datetime | DateTime / TIMESTAMP | TIMESTAMP, DATETIME |
datetime.date | Date | DATE |
datetime.time | Time | TIME |
decimal.Decimal | Numeric(precision, scale) | NUMERIC/DECIMAL |
bytes | LargeBinary / BLOB | BLOB, BYTEA |
dict / list(JSON) | JSON | JSON, JSONB |
uuid.UUID | Uuid(2.0) / CHAR(36) | UUID, CHAR(36) |
声明式 ORM 中通常用 Mapped[python_type] + mapped_column(类型或留空),SQLAlchemy 会根据 Mapped 的注解和 mapped_column 的参数决定最终列类型:
from datetime import date, datetime
from decimal import Decimal
from typing import Optional
from uuid import UUID
from sqlalchemy import String, Text, Numeric, DateTime, Date, Boolean, JSON, LargeBinary
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Product(Base):
__tablename__ = "product"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(255))
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
price: Mapped[Decimal] = mapped_column(Numeric(10, 2))
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
tags: Mapped[Optional[list]] = mapped_column(JSON, nullable=True) # 存 list/dict
binary_data: Mapped[Optional[bytes]] = mapped_column(LargeBinary, nullable=True)
- nullable:数据库允许 NULL 时,Python 侧用
Optional[...]或mapped_column(..., nullable=True),否则写入None可能违反非空约束。 - 默认值:
default=...在 Python 层赋默认值;server_default=text("...")在数据库层(如CURRENT_TIMESTAMP)。
类型不一致时
-
数据库是字符串,业务要 int/float/date
- 读:在 Python 里转,如
int(row.count_str),或用 TypeDecorator 在 ORM 层统一转(见下)。 - 写:传入目标类型(如
int),由驱动/ORM 转成库能接受的格式;若库是 VARCHAR,会按 String 存成str(123)。
- 读:在 Python 里转,如
-
数据 库是 NUMERIC/DECIMAL,Python 用 float
- 金额等用
decimal.Decimal+Numeric(precision, scale)更稳妥;用 float 会有精度问题,必要时先round(x, 2)。
- 金额等用
-
数据库是 JSON/JSONB,Python 用 dict/list
- 用 SQLAlchemy 的
JSON类型:写 dict/list,ORM 序列化为 JSON 字符串;读时自动反序列化。
- 用 SQLAlchemy 的
-
数据库是 BYTEA/BLOB,Python 用 bytes
- 用
LargeBinary,读写都是bytes。若存的是 UTF-8 字符串,取回后value.decode('utf-8')。
- 用
-
时区:datetime 无时区 vs 有时区
- 库侧:
TIMESTAMP WITH TIME ZONE(如 PostgreSQLtimestamptz)存 UTC;WITHOUT TIME ZONE只存字面值。 - Python:
datetime.utcnow()无时区;datetime.now(timezone.utc)带时区。存库统一用 UTC,展示再转本地;列可用DateTime(timezone=True)(库支持时)。
- 库侧:
TypeDecorator
库里存的是某种格式(如字符串),业务想用另一种 Python 类型(enum、自定义类)时,用 TypeDecorator 统一“存什么/读成什么”:
import json
from sqlalchemy import TypeDecorator, String
class JSONListType(TypeDecorator):
impl = String
cache_ok = True
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value, ensure_ascii=False)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
- process_bind_param:Python → 数据库(写)。
- process_result_value:数据库 → Python(读)。
列在库里仍是字符串,在 ORM 上读写则是 list/dict。
模型与表
用 DeclarativeBase 定义模型,类对应表、属性对应列:
from typing import Optional
from sqlalchemy import String, Index
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user_account"
__table_args__ = (
Index("ix_user_email", "email", unique=True),
)
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
email: Mapped[str] = mapped_column(String(255), unique=True)
fullname: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
- 主键:至少一列
primary_key=True。自增:多数数据库对整数主键默认自增;显式可用mapped_column(Integer, primary_key=True, autoincrement=True)。 - 唯一 / 索引:
unique=True单列唯一;多列唯一或复合索引用__table_args__中的UniqueConstraint、Index。 - 建表:
Base.metadata.create_all(engine)只创建不存在的表,不改已有表;上线改表结构用 Alembic 做迁移。