Pyramid-SQLAlchemy中如何避免PostgreSQL插入重复msheet_no?
问题拆解与解决方案
咱们先看看你的代码为啥会出现重复插入msheet_no的问题,然后一步步解决:
你的代码核心问题
- 新对象没赋值
msheet_no:你创建了obj1=NewEntry(),但完全没给它设置msheet_no属性,所以obj1.msheet_no一直是None,和request.params['msheet_no']的判断永远不成立,直接就走到添加对象的分支了——这是最关键的逻辑错误! - 没正确查库校验:你当前的查询是根据
batch找最大的roll_no,但完全没检查数据库里已经存在的msheet_no记录。 - 没处理并发竞态:就算代码层面加了检查,高并发场景下可能两个请求同时通过校验,然后都插入重复数据。
分步解决
1. 修正代码逻辑,正确校验记录
首先要先查库确认msheet_no是否已存在,然后再生成新的roll_no,最后给新对象赋全所有必要属性:
def nextPage(request): dbsession = request.dbsession msheet_no = request.params['msheet_no'] batch = # 这里你得补上batch变量的赋值,原代码里没看到哦 # 第一步:检查msheet_no是否已存在 existing_entry = dbsession.query(NewEntry).filter(NewEntry.msheet_no == msheet_no).first() if existing_entry: print(f"Marksheet No {msheet_no} 已经存在") # 这里可以返回前端错误响应,比如return HTTPBadRequest() return # 第二步:获取该batch下最大的roll_no,计算新序号 max_roll_entry = dbsession.query(NewEntry).filter(NewEntry.batch == batch).order_by(NewEntry.roll_no.desc()).first() rollno = max_roll_entry.roll_no + 1 if max_roll_entry else 1 # 处理batch还没记录的情况 # 第三步:创建新对象并赋值所有必填字段 obj1 = NewEntry() obj1.msheet_no = msheet_no # 这步绝对不能忘! obj1.roll_no = rollno obj1.batch = batch # 别忘了设置batch # 添加到会话,Pyramid会自动处理事务提交 dbsession.add(obj1)
2. 用数据库约束强化唯一性
你说msheet_no和roll_no是复合主键,但如果你的需求是msheet_no单独唯一(不允许重复),那得给msheet_no加个唯一约束,这是防止重复的最后一道防线:
修改你的NewEntry模型:
from sqlalchemy import Column, Integer, String, UniqueConstraint from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class NewEntry(Base): __tablename__ = 'new_entry' msheet_no = Column(String, primary_key=True) roll_no = Column(Integer, primary_key=True) batch = Column(String) # 添加msheet_no的唯一约束 __table_args__ = ( UniqueConstraint('msheet_no', name='uq_new_entry_msheet_no'), )
这样就算代码层面漏了校验,数据库会直接抛出约束违反的异常,阻止重复插入。
3. 处理并发场景的异常
高并发下可能出现两个请求同时通过代码校验,然后同时插入导致数据库报错,这时候要捕获异常并处理:
from sqlalchemy.exc import IntegrityError def nextPage(request): dbsession = request.dbsession msheet_no = request.params['msheet_no'] batch = # 你的batch变量 try: # 检查msheet_no是否存在 existing_entry = dbsession.query(NewEntry).filter(NewEntry.msheet_no == msheet_no).first() if existing_entry: print(f"Marksheet No {msheet_no} 已经存在") return # 计算新roll_no max_roll_entry = dbsession.query(NewEntry).filter(NewEntry.batch == batch).order_by(NewEntry.roll_no.desc()).first() rollno = max_roll_entry.roll_no + 1 if max_roll_entry else 1 # 创建并赋值新对象 obj1 = NewEntry(msheet_no=msheet_no, roll_no=rollno, batch=batch) dbsession.add(obj1) dbsession.flush() # 触发数据库操作,提前暴露异常 except IntegrityError: # 捕获约束异常,说明有并发插入 dbsession.rollback() print(f"Marksheet No {msheet_no} 刚被其他请求插入了") # 返回前端错误响应 return
关键提醒
- 新创建的模型对象不会自动带上请求参数的值,必须显式赋值所有必要字段!
- 数据库约束比代码校验更可靠,一定要用上,别只依赖代码检查。
- 高并发场景下必须捕获
IntegrityError,处理竞态条件导致的重复插入问题。
内容的提问来源于stack exchange,提问作者P Pariventhan
相关产品推荐
相关产品推荐

