2

我有一个表,其中主键(id)不是我区分记录的键。为此,我有 3 列的唯一约束。为了能够合并记录,我添加了一个类方法,如果存在则检索相关记录,否则返回新记录。

class Foo(Base):
    __table_args__ = (sa.UniqueConstraint('bar', 'baz', 'qux'),)

    id = sa.Column(Identifier, sa.Sequence('%s_id_seq' % __tablename__), nullable=False, primary_key=True)
    bar = sa.Column(sa.BigInteger)
    baz = sa.Column(sa.BigInteger)
    qux = sa.Column(sa.BigInteger)
    a1 = sa.Column(sa.BigInteger)
    a2 = sa.Column(sa.BigInteger)

    @classmethod
    def get(cls, bar=None, baz=None, qux=None, **kwargs):
        item = session.query(cls).\
            filter(cls.bar== bar).\
            filter(cls.baz == baz).\
            filter(cls.qux == qux).\
            first()

        if item:
            for k, v in kwargs.iteritems():
                if getattr(item, k) != v:
                    setattr(item, k, v)
        else:
            item = cls(bar=bar, baz=baz, qux=qux, **kwargs)

        return item

这在大多数情况下都很好用,但每隔一段时间,我在尝试合并项目时都会收到一个完整性错误:

foo = Foo.get(**item)
session.merge(foo)

据我了解,发生这种情况是因为合并尝试在已经存在具有唯一字段的记录的位置插入记录。

功能有问题get吗?我在这里想念什么?

(顺便说一句:我意识到这可能看起来很尴尬,但我需要一个唯一的顺序 ID,并且为了避免 DB 不支持非主键列上的序列的问题,我这样做了)

编辑 1: 将 orm.db 更改为 session 以便示例更清晰

编辑 2: 我让这个系统在多个平台上运行,似乎这只发生在 Ubuntu 之上的 mysql 中(其他平台是 RedHat 之上的 Oracle)。此外,以某种奇怪的方式,它更多地发生在特定的最终用户身上。关于 mysql,我尝试了 mysql 和 mysql+mysqldb 作为连接字符串,但都产生了这个错误。对于最终用户,这没有任何意义,我不知道该怎么做......关于mysql,

4

2 回答 2

2

Indeed your method is prone to integrity errors. What happens is that when you invoke Foo.get(1,2,3) 2 times, you do not flush the session in between. The second time you invoke it, the ORM query fails again - because there is no actual row in the DB yet - and a new object is created, with different identity. Then on commit/flush these 2 clash causing the integrity error. This can be avoided by flushing the DB after each object creation.

Now, things work differently in SQLAlchemy if the primary key is known in merge/ORM get - if the matching primary key is found already loaded in the session, SQLAlchemy realizes that these 2 must be the same object. However no such checks are done on unique indexes. It might be possible to do that also. However it would make race conditions only rarer, as there might be 2 sessions creating the same (bar, baz, qux) triplet at the same time.

TL;DR:

else:
    item = cls(bar=bar, baz=baz, qux=qux, **kwargs)
    session.add(item)
    session.flush()
于 2013-08-03T15:40:34.993 回答
0

我好像找到了罪魁祸首!

昨天我遇到了客户由于时区错误而收到 IntegrityError 的情况,所以这让我想到了那个方向。我用来识别模型的字段之一是 Date 列(我不知道它是相关的,因此我什至没有提到它,抱歉......),但是,因为我从 Flex 客户端调用操作使用 AMF,并且由于 actionscript 没有没有时间的 Date 对象,我正在传输一个时间为零的 Date 对象。我想在某些情况下,我在那些引发 IntegrityError 的日期中有不同的时间。

我本来希望 SA 在 Date 列的情况下从日期值中删除时间,因为我认为 DB 会有,所以Model.date_column == datetime_value应该在进行比较之前将 datetime 转换为 datetime.date。

至于我的解决方案,我只是确保在查询数据库之前将该值转换为 datetime.date() ......到目前为止,昨天和今天都很安静,没有任何抱怨。如果有任何变化,我会密切关注并报告...

谢谢大家的帮助。干杯,奥菲尔

于 2013-09-09T05:34:53.433 回答