3、Python与数据库连接:使用SQLAlchemy ORM、配置数据库连接池、会话管理
好,咱们进入第三个核心环节。说白了,仓库管理系统里所有货架、库存、出入库记录,最终都得落到数据库里。Python怎么跟数据库打交道?我个人的经验是,别直接写裸SQL,除非你只是想快速验证个东西。生产环境里,我强烈推荐用SQLAlchemy ORM。
为什么?你想想看,货架库存的逻辑本身就够复杂了,再掺和一堆字符串拼接的SQL语句,维护起来简直是噩梦。ORM帮你把数据库表映射成Python对象,操作起来就像在操作普通列表和字典。嗯,这里要注意,ORM不是银弹,但对我们这个场景,它非常合适。
3.1 为什么选SQLAlchemy?
我在项目中遇到过好几次团队争论,到底用原生sqlite3还是SQLAlchemy。我的结论很明确:除非你的项目只有一张表、三个字段,否则直接上SQLAlchemy。它带来的好处是实实在在的:
- 代码可读性高:你写的不是SQL,是Python。其他同事接手时,不用去猜那段SQL到底要干嘛。
- 数据库无关性:今天用SQLite开发,明天上线切MySQL,只需要改一行连接字符串。我吃过这个甜头,真的省大事。
- 防SQL注入:ORM帮你做了参数化查询,你基本不用担心注入问题。
- 会话管理:这是ORM最核心的能力之一,后面我会细讲。
核心观点:在货架库存管理这种需要频繁读写、事务复杂的场景下,SQLAlchemy ORM能帮你把数据库操作的复杂度降低一个数量级。
3.2 安装与基础配置
安装很简单,一行命令搞定。但我建议你顺便装上 pymysql 或者 psycopg2,因为生产环境很少用SQLite。
pip install sqlalchemy pymysql
接下来,我们配置数据库连接。我个人习惯把数据库配置单独放在一个 config.py 文件里,方便管理。
# config.py
DATABASE_URL = "mysql+pymysql://root:password@localhost:3306/warehouse_db?charset=utf8mb4"
# 如果是开发环境,可以用SQLite快速测试
# DATABASE_URL = "sqlite:///./warehouse.db"
然后创建引擎。引擎是数据库的核心入口,所有连接都通过它来管理。
from sqlalchemy import create_engine
engine = create_engine(
DATABASE_URL,
echo=False, # 生产环境建议关闭,调试时可以打开看SQL日志
pool_size=10, # 连接池大小
max_overflow=20, # 最大溢出连接数
pool_pre_ping=True, # 每次连接前检查是否存活
pool_recycle=3600 # 连接回收时间,1小时
)
个人经验:pool_pre_ping=True 这个参数一定要加。我曾经在生产环境遇到过数据库连接因为长时间空闲被MySQL服务端断开,结果程序还在用那个“死连接”查询,直接报错。加上这个参数后,每次拿连接前都会ping一下,确保连接是活的。
3.3 配置数据库连接池
连接池是什么?说白了,就是预先创建一批数据库连接放在池子里,谁用谁取,用完归还。避免了每次请求都创建新连接的开销。
我见过很多新手直接不配连接池,结果并发一上来,数据库连接数暴涨,直接把数据库拖垮。嗯,这里要注意,连接池不是越大越好,要根据你的并发量和数据库性能来调。
| 参数 | 说明 | 建议值 |
|---|---|---|
pool_size |
连接池中保持的最小连接数 | 5-20,根据并发量调整 |
max_overflow |
当连接池不够用时,最多额外创建多少个连接 | 10-30,不要超过数据库最大连接数的50% |
pool_recycle |
连接的最大存活时间(秒),到期后会被回收 | 3600(1小时),避免连接被数据库服务端断开 |
pool_pre_ping |
每次从池中取连接前,是否先ping一下 | True,强烈建议开启 |
避坑指南:我曾经在一个高并发项目里把 pool_size 设成了50,max_overflow 设成了100。结果数据库最大连接数只有200,高峰期直接打满,导致其他服务连不上数据库。后来我改成 pool_size=10,max_overflow=20,配合连接池等待机制,反而更稳定。记住,连接池不是越大越好,够用就行。
3.4 会话管理——ORM的核心
会话(Session)是SQLAlchemy ORM里最核心的概念。你可以把它理解成一个“工作单元”,所有对数据库的增删改查操作,都在会话里完成。
我个人的最佳实践是:使用 sessionmaker 创建一个会话工厂,然后在需要操作数据库时,用上下文管理器来获取和关闭会话。
from sqlalchemy.orm import sessionmaker
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 在业务代码中使用
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
为什么用 yield?这是为了配合FastAPI等框架的依赖注入。如果你写普通脚本,可以直接用 with 语句:
with SessionLocal() as session:
# 在这里执行数据库操作
result = session.query(SomeModel).all()
session.commit() # 提交事务
关键点:一定要记得关闭会话!不关闭会话会导致连接泄漏,最终连接池被耗尽。我见过最夸张的一次,一个同事忘了关会话,跑了三天后数据库连接数飙到500+,所有请求都超时了。
3.5 定义货架库存的ORM模型
好了,理论说完了,咱们来点实际的。假设我们要管理货架库存,需要两张核心表:Shelf(货架)和 Inventory(库存记录)。
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float
from sqlalchemy.orm import declarative_base, relationship
from datetime import datetime
Base = declarative_base()
class Shelf(Base):
__tablename__ = "shelves"
id = Column(Integer, primary_key=True, index=True)
code = Column(String(50), unique=True, nullable=False, comment="货架编号")
location = Column(String(100), comment="货架位置描述")
capacity = Column(Integer, default=100, comment="最大容量")
created_at = Column(DateTime, default=datetime.utcnow)
# 关联库存记录
inventories = relationship("Inventory", back_populates="shelf")
class Inventory(Base):
__tablename__ = "inventories"
id = Column(Integer, primary_key=True, index=True)
shelf_id = Column(Integer, ForeignKey("shelves.id"), nullable=False)
sku = Column(String(50), nullable=False, comment="商品SKU")
quantity = Column(Integer, default=0, comment="当前数量")
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联货架
shelf = relationship("Shelf", back_populates="inventories")
你看,用ORM定义模型,每个字段都清清楚楚。注释写明白,别人一看就懂。而且 relationship 让你可以直接通过 shelf.inventories 拿到这个货架上的所有库存记录,不用写JOIN。
3.6 完整的增删改查示例
最后,咱们来一个完整的CRUD示例,把上面所有知识点串起来。
# 创建表(首次运行)
Base.metadata.create_all(bind=engine)
# 新增一个货架
def create_shelf(code: str, location: str, capacity: int = 100):
with SessionLocal() as session:
new_shelf = Shelf(code=code, location=location, capacity=capacity)
session.add(new_shelf)
session.commit()
session.refresh(new_shelf) # 刷新,获取数据库生成的id
return new_shelf
# 查询某个货架上的所有库存
def get_inventories_by_shelf(shelf_code: str):
with SessionLocal() as session:
shelf = session.query(Shelf).filter(Shelf.code == shelf_code).first()
if not shelf:
return None
return shelf.inventories # 直接通过关系获取
# 更新库存数量
def update_inventory(shelf_id: int, sku: str, quantity: int):
with SessionLocal() as session:
inv = session.query(Inventory).filter(
Inventory.shelf_id == shelf_id,
Inventory.sku == sku
).first()
if inv:
inv.quantity = quantity
else:
# 如果不存在,则新建
inv = Inventory(shelf_id=shelf_id, sku=sku, quantity=quantity)
session.add(inv)
session.commit()
return inv
# 删除一个货架(同时会删除关联的库存记录,取决于级联配置)
def delete_shelf(shelf_id: int):
with SessionLocal() as session:
shelf = session.query(Shelf).get(shelf_id)
if shelf:
session.delete(shelf)
session.commit()
return True
return False
我的习惯:每个函数都独立使用 with SessionLocal() as session,这样能确保会话及时关闭。不要在函数之间传递session,容易造成混乱。如果你需要在一个事务里执行多个操作,那就把逻辑写在一个函数里,用同一个session。
好了,这一章的内容就到这里。说白了,数据库连接和会话管理是仓库管理系统的地基。地基打不好,上面盖的楼再漂亮也得塌。下一章咱们会深入讲货架库存的核心业务逻辑,包括入库、出库、盘点这些实际操作。到时候你会看到,今天学的ORM模型和会话管理,会派上大用场。