SQLAlchemy入门教程

SQLAlchemy 从零到精通,这一篇就够了

前言

如果你正在学 Python Web 开发,迟早会碰到一个问题:怎么优雅地操作数据库?

裸写 SQL 字符串拼接?辣眼睛,还容易 SQL 注入。手动管理连接开关?忘了 close 就内存泄漏。

SQLAlchemy 就是来解决这些问题的。它是 Python 生态中最成熟的 ORM 框架,Flask、FastAPI、Django 的数据库层都能看到它的影子。这篇文章带你从零开始,一步步搞懂 SQLAlchemy 的核心用法------不堆概念,直接上代码

适合人群:有 Python 基础、了解 MySQL 基本语法、没用过或刚接触 SQLAlchemy 的同学。


一、SQLAlchemy 是什么

先说人话:SQLAlchemy 是一座桥,一头连着你的 Python 对象,一头连着数据库表。

你操作 Python 对象,它在后台帮你翻译成 SQL 发给数据库;数据库返回结果,它再帮你拼装成 Python 对象。你全程不碰一行 SQL。

传统方式 SQLAlchemy ORM
手写 SQL 字符串 操作 Python 对象
手动管理连接 自动连接池管理
手动映射字段 自动对象-表映射
换数据库要改 SQL 换数据库只改连接串
SQL 注入风险 参数化查询,天然防注入

核心优势

  • 跨数据库兼容(MySQL、PostgreSQL、SQLite、Oracle 等)
  • 自动防 SQL 注入
  • 事务管理完善
  • 查询 API 丰富,复杂条件也能链式拼出来
  • 社区大,文档全,遇到问题搜得到答案

二、核心概念速览

在看代码之前,先把几个关键名词搞清楚,后面不会晕。

概念 一句话解释 类比
Engine 数据库连接的引擎入口 钥匙,开门用的
Connection URL 告诉引擎连哪个库、怎么连 门牌号
Base 所有模型的父类,继承它才叫"模型" 户口本
Model 一个类 = 一张表,属性 = 字段 房产证
Column 定义字段类型和约束 房间里的家具
Session 与数据库对话的会话,所有操作都走它 物业窗口

踩坑提醒 :很多人把 Session 和 Connection 搞混。Connection 是底层的,Session 是上层封装,日常开发只用 Session,不碰 Connection


三、环境准备

3.1 安装依赖

bash 复制代码
# 安装 SQLAlchemy 核心
pip install sqlalchemy

# 安装 MySQL 驱动(二选一)
pip install pymysql                    # 纯 Python 实现,轻量好用
# pip install mysql-connector-python  # MySQL 官方驱动,兼容性也好

验证安装是否成功:

python 复制代码
import sqlalchemy
import pymysql

print(sqlalchemy.__version__)  # 2.0.x
print(pymysql.__version__)     # 1.1.x

3.2 准备数据库

在 MySQL 里建一个数据库,后面所有操作都在这个库里进行:

sql 复制代码
-- 登录 MySQL
mysql -u root -p

-- 创建数据库
CREATE DATABASE IF NOT EXISTS sa_demo
    DEFAULT CHARACTER SET utf8mb4
    DEFAULT COLLATE utf8mb4_unicode_ci;

-- 确认
SHOW DATABASES;

踩坑提醒 :字符集一定要选 utf8mb4,不要用 utf8。MySQL 的 utf8 是阉割版,存不了 emoji 和部分生僻字。


四、从零搭建:Engine + Base + Model

4.1 统一配置文件

实际项目中,数据库配置应该集中管理,而不是每个文件都写一遍连接串。我们抽一个 db_config.py

python 复制代码
# db_config.py
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker

# 连接字符串:驱动://用户名:密码@地址:端口/库名?参数
DB_URL = "mysql+pymysql://root:123456@localhost:3306/sa_demo?charset=utf8mb4"

# 创建引擎(echo=True 会打印底层 SQL,调试时开着,上线关掉)
engine = create_engine(DB_URL, echo=True)

# 会话工厂,绑定引擎
SessionLocal = sessionmaker(bind=engine)

# 模型基类,所有 Model 继承它
Base = declarative_base()

提示declarative_base() 在 SQLAlchemy 2.0 中推荐从 sqlalchemy.orm 导入,旧版从 sqlalchemy.ext.declarative 导入,效果一样,新写法更规范。

4.2 定义模型

我们不用老套的 user 表,换一个**商品表(product)**做示例,更有实际意义:

python 复制代码
# models.py
from datetime import datetime
from sqlalchemy import Column, Integer, String, Float, DateTime, Text
from db_config import Base


class Product(Base):
    """商品模型,对应数据库中的 product 表"""
    __tablename__ = "product"

    id = Column(Integer, primary_key=True, autoincrement=True, comment="商品ID")
    name = Column(String(100), nullable=False, comment="商品名称")
    price = Column(Float, nullable=False, default=0.0, comment="价格")
    stock = Column(Integer, nullable=False, default=0, comment="库存")
    description = Column(Text, comment="商品描述")
    created_at = Column(DateTime, default=datetime.now, comment="创建时间")

    def __repr__(self):
        return f"<Product id={self.id} name={self.name} price={self.price}>"


class Category(Base):
    """分类模型,对应数据库中的 category 表"""
    __tablename__ = "category"

    id = Column(Integer, primary_key=True, autoincrement=True, comment="分类ID")
    name = Column(String(50), nullable=False, unique=True, comment="分类名称")

    def __repr__(self):
        return f"<Category id={self.id} name={self.name}>"

常用字段类型速查

SQLAlchemy 类型 MySQL 对应 用途
Integer INT 整数
String(n) VARCHAR(n) 定长字符串
Text TEXT 长文本
Float FLOAT/DOUBLE 浮点数
Boolean TINYINT(1) 布尔
DateTime DATETIME 时间
DECIMAL DECIMAL 精确小数(金额)

常用约束速查

参数 类型 作用
primary_key bool 主键
autoincrement bool 自增
nullable bool 是否允许 NULL
default Any Python 层默认值
unique bool 唯一约束
comment str 字段注释
index bool 索引

4.3 创建表

python 复制代码
# init_db.py
from db_config import engine, Base
from models import Product, Category  # 必须导入,否则 Base 不知道有这些表

# 根据所有继承 Base 的模型,在数据库中创建对应表
# 如果表已存在则跳过,不会报错
Base.metadata.create_all(engine)

print("表创建完成!")

执行后,可以在 MySQL 中确认:

sql 复制代码
SHOW TABLES;
-- 应该能看到 product 和 category 两张表

DESC product;
-- 查看表结构,确认字段类型和约束

踩坑提醒create_all 只创建不更新。如果你后来给模型加了字段,create_all 不会自动在表里加列。需要用 Alembic(SQLAlchemy 的迁移工具)或手动 ALTER TABLE。


五、Session:操作数据库的唯一入口

所有增删改查都通过 Session 完成。三种使用方式对比:

python 复制代码
from db_config import SessionLocal
from models import Product

# ===== 方式一:手动开关(不推荐,容易忘关) =====
session = SessionLocal()
try:
    product = Product(name="键盘", price=199.0, stock=100)
    session.add(product)
    session.commit()
finally:
    session.close()

# ===== 方式二:with 上下文(推荐日常用) =====
with SessionLocal() as session:
    product = Product(name="鼠标", price=59.0, stock=200)
    session.add(product)
    session.commit()
# 出 with 块自动 close,commit 需要手动调用

# ===== 方式三:函数封装(推荐工程用) =====
def get_session():
    """统一会话获取入口"""
    return SessionLocal()

with get_session() as session:
    # 业务逻辑写这里
    pass

踩坑提醒with SessionLocal() as session 在退出时只关闭连接,不会自动 commit 。你在 with 块里改了数据,必须显式调 session.commit(),否则改动不生效。这是新手最常踩的坑。


六、CRUD 核心操作

6.1 Create ------ 新增数据

python 复制代码
from db_config import SessionLocal
from models import Product

with SessionLocal() as session:
    # --- 单条新增 ---
    p1 = Product(name="机械键盘", price=399.0, stock=50, description="青轴,段落感强")
    session.add(p1)
    session.commit()  # 提交后才真正写入数据库
    print(f"新增成功,ID={p1.id}")  # commit 后 id 自动回填

    # --- 多条新增 ---
    p2 = Product(name="显示器", price=1599.0, stock=20)
    p3 = Product(name="鼠标垫", price=19.9, stock=500)
    session.add_all([p2, p3])
    session.commit()

    # --- 批量字典新增(高效,适合大量数据) ---
    products = [
        {"name": "U盘", "price": 39.0, "stock": 300},
        {"name": "移动硬盘", "price": 299.0, "stock": 150},
        {"name": "读卡器", "price": 15.0, "stock": 800},
    ]
    session.bulk_insert_mappings(Product, products)
    session.commit()
方法 适用场景 特点
add() 单条 自动回填 id
add_all() 少量多条 对象已创建,逐个 add
bulk_insert_mappings 大批量 不实例化对象,效率高,但不回填 id

6.2 Read ------ 查询数据

基础查询
python 复制代码
from db_config import SessionLocal
from models import Product

with SessionLocal() as session:
    # 1. 查全部(SELECT * FROM product)
    all_products = session.query(Product).all()
    for p in all_products:
        print(p)

    # 2. 按主键查一条(SELECT * FROM product WHERE id=1)
    product = session.get(Product, 1)
    print(product)

    # 3. 查第一条(SELECT * FROM product LIMIT 1)
    first = session.query(Product).first()
    print(first)

    # 4. 只查指定字段(SELECT name, price FROM product)
    rows = session.query(Product.name, Product.price).all()
    for row in rows:
        print(f"名称:{row.name},价格:{row.price}")
条件查询
python 复制代码
from sqlalchemy import and_, or_

with SessionLocal() as session:
    # 等于:WHERE price = 199.0
    r = session.query(Product).filter(Product.price == 199.0).all()

    # 不等于:WHERE price != 199.0
    r = session.query(Product).filter(Product.price != 199.0).all()

    # 大于/小于:WHERE price > 100
    r = session.query(Product).filter(Product.price > 100).all()

    # 模糊查询:WHERE name LIKE '%盘%'
    r = session.query(Product).filter(Product.name.like("%盘%")).all()

    # IN 查询:WHERE id IN (1, 2, 3)
    r = session.query(Product).filter(Product.id.in_([1, 2, 3])).all()

    # 多条件 AND:WHERE price > 100 AND stock > 50
    r = session.query(Product).filter(
        and_(Product.price > 100, Product.stock > 50)
    ).all()

    # 多条件 OR:WHERE price < 50 OR stock > 500
    r = session.query(Product).filter(
        or_(Product.price < 50, Product.stock > 500)
    ).all()
排序、分页、聚合
python 复制代码
from sqlalchemy import func

with SessionLocal() as session:
    # 排序:ORDER BY price DESC
    r = session.query(Product).order_by(Product.price.desc()).all()

    # 分页:每页10条,取第2页
    page, size = 2, 10
    r = session.query(Product).order_by(Product.id) \
        .limit(size).offset((page - 1) * size).all()

    # 统计总数:SELECT COUNT(*) FROM product
    total = session.query(Product).count()

    # 聚合查询:按价格区间统计商品数量
    # SELECT price, COUNT(*) FROM product GROUP BY price
    r = session.query(
        Product.price,
        func.count(Product.id).label("cnt")
    ).group_by(Product.price).all()
    for row in r:
        print(f"价格 {row.price}:{row.cnt} 件商品")

    # HAVING 筛选分组
    r = session.query(
        Product.price,
        func.count(Product.id).label("cnt")
    ).group_by(Product.price).having(func.count(Product.id) > 1).all()

分页公式offset = (页码 - 1) × 每页条数。第 1 页 offset=0,第 2 页 offset=10。

6.3 Update ------ 更新数据

python 复制代码
from db_config import SessionLocal
from models import Product

with SessionLocal() as session:
    # --- 单条更新:先查后改 ---
    product = session.query(Product).filter(Product.name == "机械键盘").first()
    if product:
        product.price = 499.0        # 改价格
        product.stock = 30           # 改库存
        session.commit()
        print(f"更新后:{product}")

    # --- 批量更新:WHERE name LIKE '%盘%' ---
    session.query(Product).filter(
        Product.name.like("%盘%")
    ).update({"price": 29.9}, synchronize_session=False)
    session.commit()

踩坑提醒 :批量 update()synchronize_session=False 参数很重要。默认情况下 Session 会在内存中同步对象状态,批量操作时这个行为可能出错或报错,显式关掉更安全。

6.4 Delete ------ 删除数据

python 复制代码
from db_config import SessionLocal
from models import Product

with SessionLocal() as session:
    # --- 单条删除:先查后删 ---
    product = session.query(Product).filter(Product.name == "读卡器").first()
    if product:
        session.delete(product)
        session.commit()

    # --- 批量删除:WHERE stock = 0 ---
    session.query(Product).filter(Product.stock == 0).delete()
    session.commit()

踩坑提醒 :删除不可逆,生产环境建议用软删除(加 is_deleted 字段标记),而不是真 DELETE。数据恢复的成本远大于多加一个字段的成本。


七、完整项目结构参考

复制代码
sa_demo/
├── db_config.py      # 数据库配置(Engine、Session、Base)
├── models.py         # 模型定义
├── init_db.py        # 创建表
├── crud/
│   ├── create.py     # 新增操作
│   ├── read.py       # 查询操作
│   ├── update.py     # 更新操作
│   └── delete.py     # 删除操作
└── main.py           # 入口

把配置和操作分离,比把所有代码塞在一个文件里清晰得多。项目大了以后这个结构会帮你省很多事。


八、总结

回顾一下全文的核心脉络:

步骤 关键动作 核心代码
1. 连数据库 创建 Engine create_engine(url)
2. 定义模型 继承 Base,写字段 class Product(Base)
3. 建表 根据模型生成表 Base.metadata.create_all(engine)
4. 开会话 创建 Session SessionLocal()
5. 增 add + commit session.add(obj)
6. 查 query + filter session.query(Model).filter(...)
7. 改 查出来改属性 obj.field = new_value
8. 删 查出来 delete session.delete(obj)
9. 提交 commit 生效 session.commit()
10. 关会话 with 自动关 / 手动 close with SessionLocal() as session

几个新手必知的原则

  1. 改了数据一定要 commit(),否则白改
  2. with 管理 Session 比手动开关节省心智
  3. 生产环境关掉 echo=True,日志会刷爆
  4. 不要在 Session 没关闭时做耗时操作,连接池有限
  5. 复杂查询写不出 ORM 链时,可以混用原生 SQL,不丢人
相关推荐
梦帮科技1 小时前
RNS 代币架构:ERC20 五件套扩展与六钱包分配
人工智能·sql·区块链·database·合成复用原则·加密货币
右耳朵猫AI1 小时前
Go周刊2026W37 | Ebitengine 纯 Go 化、simd 重写 TurboPFor、json/v2 落地
后端·微服务·go
devpotato1 小时前
RPO与RTO:容灾的两个关键指标
java·后端
BUG研究员_1 小时前
LangGraph持久化之失败后恢复运行
python·agent
gb42152871 小时前
数字人面试和RAG区别?
python
宁渡AI大模型1 小时前
AI 全栈面试新趋势:Vibe Coding、前端、Java 后端高频面试题深度解析|河南宁渡科技有限公司编程教程
java·javascript·人工智能·python·ai大模型
Groundwork Explorer2 小时前
ESP32-C3 SuperMini 排查WIFI收发故障
python·单片机·嵌入式硬件·mcu
codigger2 小时前
程序员别再踩这 3 个坑——做了五年开发,我把能踩的坑全踩了一遍
后端·ai·程序员·架构·程序员职场
智购科技自动售货机工厂2 小时前
2026自动售货机端侧AI降本逻辑:从云端API到本地推理的成本重构~YH
人工智能·python·ui·面试·交互