前提准备
- 先安装依赖:
bash
pip install pymysql sqlalchemy
- 确保本地MySQL已启动,且提前创建好测试数据库(比如
test_sqlalchemy),账号密码替换成你自己的。
基础配置与表模型定义
这是所有示例的公共基础代码,先定义数据库连接、表模型(Python类映射MySQL表),后续所有操作都基于这段代码,只需一次性写好。
python
# 导入核心依赖
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey
from sqlalchemy.orm import sessionmaker, declarative_base, relationship
from typing import List
# 1. 配置数据库连接URL(核心:mysql+pymysql指定底层驱动为PyMySQL)
# 格式:mysql+pymysql://用户名:密码@主机:端口/数据库名?字符集参数
DB_URL = "mysql+pymysql://root:123456@localhost:3306/test_sqlalchemy?charset=utf8mb4"
# 2. 创建引擎(SQLAlchemy自动管理连接池,无需手动处理PyMySQL连接)
# pool_size:默认连接数,max_overflow:最大临时连接数
engine = create_engine(
DB_URL,
pool_size=5,
max_overflow=10,
echo=False # echo=True会打印生成的原生SQL,方便调试,生产环境设为False
)
# 3. 声明ORM基类(所有表模型都要继承这个基类)
Base = declarative_base()
# 4. 定义表模型(示例:用户表User + 商品表Goods + 订单表Order,含外键关联)
# 【用户表】
class User(Base):
__tablename__ = "user" # 必须指定,对应MySQL中的实际表名
# 字段定义:Column(类型, 约束)
id = Column(Integer, primary_key=True, autoincrement=True, comment="用户ID")
name = Column(String(50), nullable=False, comment="用户名")
age = Column(Integer, comment="用户年龄")
phone = Column(String(20), unique=True, comment="手机号,唯一约束")
# 关联关系:一个用户有多个订单(反向关联Order.user),lazy="select"按需查询
orders: List["Order"] = relationship("Order", back_populates="user", lazy="select")
# 【商品表】
class Goods(Base):
__tablename__ = "goods"
id = Column(Integer, primary_key=True, autoincrement=True, comment="商品ID")
name = Column(String(100), nullable=False, comment="商品名")
price = Column(Float, nullable=False, comment="商品价格")
stock = Column(Integer, default=0, comment="库存,默认0")
# 关联关系:一个商品被多个订单包含
orders: List["Order"] = relationship("Order", back_populates="goods", lazy="select")
# 【订单表】(含外键,关联User和Goods)
class Order(Base):
__tablename__ = "order"
id = Column(Integer, primary_key=True, autoincrement=True, comment="订单ID")
user_id = Column(Integer, ForeignKey("user.id"), nullable=False, comment="关联用户表ID")
goods_id = Column(Integer, ForeignKey("goods.id"), nullable=False, comment="关联商品表ID")
num = Column(Integer, nullable=False, default=1, comment="购买数量")
# 反向关联:订单属于一个用户、对应一个商品
user = relationship("User", back_populates="orders")
goods = relationship("Goods", back_populates="orders")
# 5. 创建所有表(SQLAlchemy自动生成CREATE TABLE语句,通过PyMySQL执行)
# 若表已存在,不会重复创建,放心执行
Base.metadata.create_all(engine)
# 6. 创建会话(Session:相当于PyMySQL的游标,操作数据库的核心入口)
# 绑定引擎,创建会话工厂,每次操作都生成一个新的session
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 封装获取会话的函数(开发中常用,避免重复写)
def get_db():
db = SessionLocal()
try:
yield db # 生成会话对象,供外部使用
finally:
db.close() # 无论是否异常,最终关闭会话
# 测试获取会话(后续所有操作都从这里获取db)
db = next(get_db())
示例1:新增数据(单条新增 + 批量新增 + 关联新增)
新增是开发最基础的操作,SQLAlchemy通过实例化模型对象 + db.add()/add_all() 实现,无需写INSERT语句。
python
# ========== 1. 单条新增(用户) ==========
new_user = User(name="张三", age=25, phone="13800138000")
db.add(new_user) # 添加单个对象
db.commit() # 提交事务(必须)
db.refresh(new_user) # 刷新对象,获取数据库自动生成的id(比如自增ID)
print(f"单条新增用户成功,ID:{new_user.id}") # 输出:单条新增用户成功,ID:1
# ========== 2. 批量新增(商品) ==========
new_goods_list = [
Goods(name="Python编程实战", price=59.9, stock=100),
Goods(name="SQLAlchemy从入门到精通", price=69.9, stock=80),
Goods(name="MySQL优化指南", price=49.9, stock=150)
]
db.add_all(new_goods_list) # 批量添加多个对象
db.commit()
# 批量刷新,获取自增ID
for goods in new_goods_list:
db.refresh(goods)
print(f"批量新增商品成功,商品ID分别为:{[g.id for g in new_goods_list]}") # [1,2,3]
# ========== 3. 关联新增(订单,关联已存在的用户和商品) ==========
# 新增订单:用户1购买商品1,数量2;用户1购买商品2,数量1
new_order1 = Order(user_id=1, goods_id=1, num=2)
new_order2 = Order(user_id=1, goods_id=2, num=1)
db.add_all([new_order1, new_order2])
db.commit()
print("关联新增订单成功")
示例2:查询数据(单条查询 + 多条查询 + 多条件过滤 + 排序/分页)
查询是最高频的操作,SQLAlchemy提供链式调用的查询语法,替代原生SQL的WHERE/ORDER BY/LIMIT,支持灵活的条件组合,无需手动拼接SQL。
python
from sqlalchemy import and_, or_ # 导入多条件逻辑符
# ========== 1. 单条查询(按主键/唯一键,推荐first()) ==========
# 按主键查询用户(id=1)
user1 = db.query(User).filter(User.id == 1).first()
if user1:
print(f"单条查询用户:ID={user1.id},姓名={user1.name},手机号={user1.phone}")
# 按唯一键查询商品(name="Python编程实战")
goods1 = db.query(Goods).filter(Goods.name == "Python编程实战").first()
print(f"按唯一键查询商品:{goods1.name},价格={goods1.price}")
# ========== 2. 多条查询(all(),获取所有符合条件的结果) ==========
# 查询所有商品
all_goods = db.query(Goods).all()
print("所有商品:", [g.name for g in all_goods])
# 查询库存>90的商品
stock_goods = db.query(Goods).filter(Goods.stock > 90).all()
print("库存>90的商品:", [g.name for g in stock_goods]) # Python编程实战、MySQL优化指南
# ========== 3. 多条件查询(and_/or_,替代SQL的AND/OR) ==========
# 条件1:年龄>=20 且 手机号以138开头(and_可省略,链式filter就是AND)
user_and = db.query(User).filter(User.age >= 20, User.phone.like("138%")).first()
# 等价于用and_显式写
user_and2 = db.query(User).filter(and_(User.age >= 20, User.phone.like("138%"))).first()
# 条件2:价格<50 或 库存<90(必须用or_)
goods_or = db.query(Goods).filter(or_(Goods.price < 50, Goods.stock < 90)).all()
print("价格<50或库存<90的商品:", [g.name for g in goods_or]) # SQLAlchemy从入门到精通、MySQL优化指南
# ========== 4. 排序(order_by,asc()升序/desc()降序,默认升序) ==========
# 商品按价格降序排列
goods_order = db.query(Goods).order_by(Goods.price.desc()).all()
print("商品按价格降序:", [g.name for g in goods_order]) # SQLAlchemy→Python→MySQL
# ========== 5. 分页(slice/limit+offset,适合前端分页) ==========
# 分页逻辑:第1页,每页2条(slice(起始索引, 结束索引),索引从0开始)
page1_goods = db.query(Goods).order_by(Goods.id).slice(0, 2).all()
print("第1页商品(2条):", [g.name for g in page1_goods])
# 等价于limit+offset(更易理解,offset是偏移量,limit是条数)
page1_goods2 = db.query(Goods).order_by(Goods.id).offset(0).limit(2).all()
# ========== 6. 只查询指定字段(替代SQL的SELECT name, price FROM goods) ==========
goods_fields = db.query(Goods.name, Goods.price).all()
print("只查询商品名和价格:", goods_fields) # 输出:[('Python编程实战', 59.9), ...]
示例3:修改数据(单条修改 + 批量修改)
修改分基于对象的修改 (先查后改,适合单条/少量修改)和直接批量修改(filter+update,适合大量数据,效率更高),无需写UPDATE语句。
python
# ========== 1. 基于对象的修改(先查后改,推荐单条修改) ==========
# 修改用户1的年龄为26,手机号为13800138001
user1 = db.query(User).filter(User.id == 1).first()
if user1:
user1.age = 26
user1.phone = "13800138001"
db.commit() # 直接提交,无需额外add
print(f"修改用户成功:{user1.name},新年龄={user1.age},新手机号={user1.phone}")
# ========== 2. 批量修改(filter+update,适合大量数据) ==========
# 所有商品价格涨5元(update返回受影响的行数)
affected_rows = db.query(Goods).update({Goods.price: Goods.price + 5})
db.commit()
print(f"批量修改商品价格成功,受影响行数:{affected_rows}") # 3行
# 条件批量修改:库存<100的商品,库存加20
db.query(Goods).filter(Goods.stock < 100).update({Goods.stock: Goods.stock + 20})
db.commit()
print("库存<100的商品库存+20成功")
示例4:删除数据(单条删除 + 批量删除 + 逻辑删除推荐)
删除分物理删除 (直接从数据库删除,不可逆)和逻辑删除 (推荐生产环境,加is_delete字段,标记为删除,不实际删数据),无需写DELETE语句。
python
# ========== 1. 单条物理删除(先查后删,适合单条数据) ==========
# 删除订单id=1的记录
order1 = db.query(Order).filter(Order.id == 1).first()
if order1:
db.delete(order1)
db.commit()
print("单条删除订单成功")
# ========== 2. 批量物理删除(filter+delete,适合大量数据) ==========
# 删除用户1的所有订单
affected_rows = db.query(Order).filter(Order.user_id == 1).delete()
db.commit()
print(f"批量删除订单成功,受影响行数:{affected_rows}")
# ========== 3. 逻辑删除(生产环境推荐,避免数据丢失) ==========
# 步骤:1. 给表加is_delete字段(Integer,default=0,0=未删,1=已删)
# 2. 删除时仅修改is_delete=1,查询时过滤is_delete=0
# 示例:给User表加字段(需重新执行Base.metadata.create_all(engine))
# is_delete = Column(Integer, default=0, comment="逻辑删除:0=未删,1=已删")
# 逻辑删除用户:
# db.query(User).filter(User.id == 1).update({User.is_delete: 1})
# 逻辑查询:
# db.query(User).filter(User.is_delete == 0).all()
示例5:连表查询(基于外键的关联查询,替代SQL的JOIN)
开发中经常需要多表联查(比如查询订单的同时,获取所属用户和对应商品的信息),SQLAlchemy通过模型关联关系 (relationship)实现无JOIN语句的连表查询,比原生SQL更简洁。
python
# 先重新新增订单(方便测试连表)
db.add_all([
Order(user_id=1, goods_id=1, num=2),
Order(user_id=1, goods_id=2, num=1)
])
db.commit()
# ========== 1. 正向连表:从用户查订单,再从订单查商品 ==========
# 查询用户1的所有订单,并获取每个订单的商品信息
user1 = db.query(User).filter(User.id == 1).first()
if user1:
print(f"用户{user1.name}的所有订单:")
for order in user1.orders:
# 通过order.goods直接获取关联的商品对象(relationship自动连表)
print(f" 订单ID:{order.id},购买商品:{order.goods.name},价格:{order.goods.price},数量:{order.num}")
# ========== 2. 反向连表:从商品查订单,再从订单查用户 ==========
# 查询商品1的所有订单,并获取每个订单的用户信息
goods1 = db.query(Goods).filter(Goods.id == 1).first()
if goods1:
print(f"商品{goods1.name}的所有订单:")
for order in goods1.orders:
print(f" 订单ID:{order.id},购买用户:{order.user.name},手机号:{order.user.phone}")
# ========== 3. 显式连表查询(join,适合复杂多表联查,替代SQL的INNER JOIN) ==========
# 显式连表:Order表JOIN User表(基于user_id外键),查询订单+用户信息
order_user = db.query(Order, User).join(User, Order.user_id == User.id).filter(User.id == 1).all()
print("显式连表查询订单和用户:")
for order, user in order_user:
print(f" 订单ID:{order.id},用户:{user.name},商品ID:{order.goods_id}")
示例6:事务处理(核心,保证数据一致性)
事务的核心是原子性 (要么全部成功,要么全部失败),比如"扣商品库存 + 生成订单"必须同时成功,否则回滚。SQLAlchemy通过db.rollback()实现回滚,无需手动处理PyMySQL的事务。
python
# 示例场景:用户1购买商品3,数量5------步骤:1. 检查商品库存 2. 扣库存 3. 生成订单
# 所有步骤必须在一个事务中,任意一步失败则全部回滚
try:
# 1. 查询商品和用户
user = db.query(User).filter(User.id == 1).first()
goods = db.query(Goods).filter(Goods.id == 3).first()
buy_num = 5
# 2. 校验库存
if not user or not goods:
raise Exception("用户或商品不存在")
if goods.stock < buy_num:
raise Exception("商品库存不足")
# 3. 扣库存
goods.stock -= buy_num
# 4. 生成订单
new_order = Order(user_id=user.id, goods_id=goods.id, num=buy_num)
db.add(new_order)
# 5. 提交事务(所有步骤成功,提交)
db.commit()
db.refresh(new_order)
print(f"购买成功!订单ID:{new_order.id},商品剩余库存:{goods.stock}")
except Exception as e:
# 任意步骤失败,回滚事务(数据恢复到操作前状态)
db.rollback()
print(f"购买失败,事务回滚:{str(e)}")
示例7:原生SQL兼容(SQLAlchemy也能执行原生SQL)
如果遇到复杂SQL (比如多表嵌套联查、存储过程、分组统计),ORM语法难以表达时,SQLAlchemy支持直接执行原生SQL,兼顾ORM的便捷和原生SQL的灵活性。
python
# ========== 1. 执行原生查询SQL(select) ==========
# 查询所有商品,返回字典格式(方便解析)
sql1 = "SELECT id, name, price, stock FROM goods WHERE stock > 100;"
result1 = db.execute(sql1).mappings().all() # mappings():结果转字典
print("原生SQL查询结果:", result1) # [{'id':1, 'name':'Python编程实战', ...}, ...]
# ========== 2. 执行带参数的原生SQL(防SQL注入,必须用%s占位符) ==========
# 查询价格大于指定值的商品,参数传元组,避免手动拼接SQL
sql2 = "SELECT name, price FROM goods WHERE price > %s;"
result2 = db.execute(sql2, (60,)).mappings().all()
print("带参数原生SQL查询结果:", result2) # 价格>60的商品
# ========== 3. 执行原生增删改SQL ==========
# 新增商品
sql3 = "INSERT INTO goods (name, price, stock) VALUES (%s, %s, %s);"
db.execute(sql3, ("Python进阶教程", 79.9, 50))
# 修改商品价格
sql4 = "UPDATE goods SET price = 89.9 WHERE name = %s;"
db.execute(sql4, ("Python进阶教程",))
# 删除商品
sql5 = "DELETE FROM goods WHERE name = %s;"
db.execute(sql5, ("Python进阶教程",))
db.commit() # 增删改必须提交事务
print("原生SQL增删改执行成功")
开发实用小技巧
- echo=True调试 :创建引擎时设
echo=True,SQLAlchemy会打印生成的原生SQL,方便调试ORM语句是否符合预期; - 连接池配置 :生产环境合理设置
pool_size(默认5)和max_overflow(默认10),避免连接数过多导致MySQL报错; - 避免N+1查询问题 :关联查询时,用
joinedload提前加载关联对象(比如db.query(User).options(joinedload(User.orders)).first()),避免多次查询数据库; - 逻辑删除优先 :生产环境尽量不用物理删除,加
is_delete/delete_time字段实现逻辑删除,方便数据恢复; - 异常捕获 :所有数据库操作都要加
try/except,异常时执行db.rollback(),避免事务挂起。
核心总结
SQLAlchemy搭配PyMySQL的使用核心要点:
- 驱动无需手动操作 :只需在连接URL写
mysql+pymysql,安装PyMySQL依赖,SQLAlchemy会自动调用; - 核心入口是Session :所有数据库操作都通过
db(Session对象)实现,替代PyMySQL的连接和游标; - ORM核心是对象映射:表→Python类,字段→类属性,数据操作→对象的增删改查,无需写原生SQL;
- 兼顾灵活性:复杂场景可直接执行原生SQL,ORM和原生SQL可无缝切换;
- 事务自动管理 :通过
db.commit()提交、db.rollback()回滚,保证数据一致性。