SQLAlchemy - 独家资源分配

时间:2015-02-03 02:49:29

标签: python transactions sqlalchemy

我正在编写一个维护某种独家资源的模块(在本例中是电影票)。模型类就像

class User(Base):
    __tablename__ = 'user'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String)

class Ticket(Base):
    __tablename__ = 'ticket'

    id = Column('id', Integer, primary_key=True)
    user_id = Column('user_id', ForeignKey('user.id'))
    user = relationship(User)
    seat = Column('seat', String)

可能会同时要求购买门票,但当然每个门票只能出售给一个用户。所以我写了一些这样的代码

def acquire_multiple(session, user_id, ticket_id_list):
    session.begin()
    try:
        for tid in ticket_id_list:
            ticket = session.query(Ticket).filter(Ticket.id == tid, Ticket.user_id == None).first()
            if ticket is None:
                raise RuntimeError('ticket sold')
            ticket.user_id = user_id
            session.add(ticket)
        session.commit()
    except:
        session.rollback()
        raise

想知道这是否有用,还是有更好的计划?

1 个答案:

答案 0 :(得分:0)

我不是故意粗鲁,但我只想分享一些绝对基本的核心编码原则,这些原则需要在您的代码中解决。

请不要提出RuntimeError。他们出于某种原因而运行。 此外,如果你想捕获一个异常,你应该捕获实际的异常,而不是写"除了:",否则你可能会捕获一个你没有打算处理的异常并且可能会出现意外结果。此外,请永远不要捕获您在try块中抛出的异常。 您的:

if ticket is None:
    raise RuntimeError('ticket sold')
...
except:
    session.rollback()
...

实际上可以改写为:

if not ticket:
    session.rollback()
    raise ValueError('ticket sold')

是pythonic,是写它的正确方法。你实际上可以将整个for循环重写为:

if ticket not in ticket_id_list:
    raise ...

所以你不必在python中迭代它(而不是C中的内部for循环)。你也应该拿

ticket = session.query(Ticket).filter(Ticket.id == tid, Ticket.user_id == None).first()

在for循环之外,并将其放在它上面,这样你就不会进行额外的函数调用,并且如果你保持for循环就会添加到堆栈中。

现在针对实际问题:您需要关联从threading.Lock()或threading.RLock()获取的票证列表中的锁定。列表的某些方面可以是线程安全的,但最好不要假设。通过它的for循环也不是。您需要锁定以确保机票不会出现两次并且仅向一位客户提供。说实话,从架构的角度来看,我会让方法返回票据(如果可用)或者如果它不可用则返回,并处理它的级别,或者将它一直冒出来并且将它返回到视图中,让它处理出售的"门票"在那里得到一个NoneType的消息.. 但你可以这样做:

lock = threading.RLock()

def acquire_multiple(session, user_id, ticket_id_list):
    session.begin()
    with lock:
        try:
            ticket = session.query(Ticket).filter(Ticket.id == tid, Ticket.user_id == None).first()
            try:
                ticket_id_list.remove(ticket)
            except ValueError:
                print("ticket sold")
                session.add(None)
            else:
                session.add(ticket)
        except AttributeError:
            print("No ticket has been selected this session!")
            session.add(None)
        finally:
            session.commit()

当你退出这个功能时,你将放开锁定,然后其他人可以尝试选择机票并且它已经被售出。

绝对有必要进行两次尝试。第一个是防止在你的会话中没有票,第二个是被认为是" pythonic",或者"请求宽恕,而不是许可",所以我们假设它'列表中的s并处理它不在列表中的可能性。在某些情况下,检查列表中的列表是否更好(在循环中表现更快),这不是其中之一。