在SQLAlchemy中插入或更新重复键(IntegrityError)

0 投票
1 回答
3967 浏览
提问于 2025-04-18 10:15

我正在尝试解析一些RSS源,并使用flask-sqlalchemy把网址写入数据库。

这些源有些内容是重复的(也就是说,同一篇文章可能出现在多个源中,或者在同一个源中出现多次),所以我把主键定义为网址的一个(非随机的)哈希值。

我遇到的问题是,当我循环处理这些网址并把它们添加到会话中时,在尝试提交到数据库时出现了异常。

这是我的代码:

for entry in feed.entries:
    # Create the database row
    article = Article(entry)

    # If the row already exists in the database
    if db.session.query(Article).filter_by(uuid=article.uuid).first():
        print "duplicate"
    else:
        db.session.merge(article)

db.session.commit()

当文章已经存在于数据库中时,它会被忽略。然而,如果它在会话中存在但还没有提交到数据库,那么SQLAlchemy会尝试在同一个事务中多次写入,这样我就会收到sqlalchemy.exc.IntegrityError: (IntegrityError) column hash is not unique的错误。

我的直觉是,我需要检查一下这个哈希值的对象在会话中是否已经存在(同时也要查询数据库),我以为使用merge可以做到这一点,但我还是遇到了这个错误。

1 个回答

6

你应该使用示例中的唯一对象模式

这个模式会在内存中为会话创建一个缓存,确保每次只得到一个唯一的结果。如果这个实例已经在缓存里,就直接使用它;如果不在,就尝试从数据库中获取。如果数据库里也没有,那就创建一个新的实例,并把它添加到缓存中。

这是我对这个模式的理解,简化了链接中的示例。

class UniqueMixin(object):
    @classmethod
    def get_unique(cls, **kwargs):
        session = current_app.extensions['sqlalchemy'].db.session
        session._unique_cache = cache = getattr(session, '_unique_cache', {})
        key = (cls, tuple(kwargs.items()))
        o = cache.get(key)

        if o is None:
            o = session.query(cls).filter_by(**kwargs).first()

            if o is None:
                o = cls(**kwargs)
                session.add(o)

            cache[key] = o

        return o

class MyModel(UniqueMixin, db.Model):
    # ...
    name = db.Column(db.String, nullable=False)
    # ...

m1 = MyModel.get_unique(name='test')  # new instance
m2 = MyModel.get_unique(name='test')  # from cache
assert m1 is m2
db.session.commit()  # inserts one row

m1 = MyModel.get_unique(name='test')  # from database
m2 = MyModel.get_unique(name='test')  # from cache
assert m1 is m2

在任何可能遇到这种情况的模型中继承UniqueMixin,并使用get_unique代替普通的构造函数。

撰写回答