如何在多主键表中插入带自增 ID 的行?

0 投票
2 回答
2310 浏览
提问于 2025-04-15 14:02

我正在写一个turbogears2的应用程序。我有一个这样的表:

class Order(DeclarativeBase):
    __tablename__ = 'order'

    # id of order
    id = Column(Integer, autoincrement=True, primary_key=True)

    # buyer's id
    buyer_id = Column(Integer, ForeignKey('user.user_id',
        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)

我想在这个表里插入一行新的数据,但出现了一个错误:“字段 'order_id' 没有默认值”。看起来我必须手动设置订单的ID,因为我有两个主键。我的问题是,怎么才能插入一行数据,让ID自动生成呢?

如果我手动生成ID,就会遇到一些问题。例如:

maxId = DBSession.query(func.max(Order)).one()[0]
newOrder = Order(id=maxId + 1, buyer_id=xxx)
DBSession.add(newOrder)

以这种方式添加新订单看起来没问题,但如果有两个请求几乎同时运行这些代码,就会出现问题。

假设请求a和b按以下顺序运行这段代码:

a.maxId = DBSession.query(func.max(Order)).one()[0]
b.maxId = DBSession.query(func.max(Order)).one()[0]
b.newOrder = Order(id=maxId + 1, buyer_id=xxx)
b.DBSession.add(newOrder)
a.newOrder = Order(id=maxId + 1, buyer_id=xxx)
a.DBSession.add(newOrder)

那么请求a可能会失败,因为表中已经有一个相同ID的订单。我可以捕捉到这个异常并重试。但我在想,有没有更好的方法呢?

有时候,ID并不是简单的整数,我们可能需要这样的订单ID:

2009090133 表示2009年9月1日的第33个订单

在这种情况下,自增功能就不能用了。所以我别无选择,只能手动为订单分配ID。那么我的另一个问题是,除了捕捉异常并重试插入带ID的行,还有没有更好的方法呢?

2 个回答

1

如果你想给每个买家的订单分配连续的编号,那么你需要在插入订单时对每个买家的交易进行排序。你可以通过对买家的数据行进行独占锁定来实现这一点:

sess.query(Buyer.id).with_lockmode('update').get(xxx)
order_id = sess.query(func.max(Order.id)+1).filter_by(buyer_id=xxx).scalar() or 1
sess.add(Order(id=order_id, buyer_id=xxx))

使用这种方法时,如果有两个交易同时尝试为同一个买家插入订单,其中一个会在第一行被阻塞,直到另一个交易完成或失败。

1

你应该在列定义中使用默认值

id = Column(Integer, default = sqlexpression)

这里的sqlexpression可以是一个SQL表达式。你可以查看这个文档。对于自增字段,你应该使用这个SQL表达式 coalesce(select max(order.id) from order,0) + 1。为了方便,你可以导入sqlalchemy.sql.text,这样id列看起来就像下面这样

id = Column(Integer, default = text("coalesce(select max(order.id) from order,0) + 1"))

撰写回答