我有下表:
class Feedback(Base):
__tablename__ = 'feedbacks'
__table_args__ = (UniqueConstraint('user_id', 'look_id'),)
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
look_id = Column(Integer, ForeignKey('looks.id'), nullable=False)
我目前在此表中插入了许多违反UniqueConstraint的条目。
我使用以下代码:
for comment in session.query(Comment).filter(Comment.type == Comment.TYPE_LOOK).yield_per(100):
feedback = Feedback()
feedback.user_id = User.get_or_create(comment.model_id).id
feedback.look_id = comment.commentable_id
session.add(feedback)
try: # Refer to T20
session.flush()
except IntegrityError,e:
print "IntegrityError", e
session.rollback()
session.commit()
我收到以下错误:
IntegrityError (IntegrityError) duplicate key value violates unique constraint "feedbacks_user_id_look_id_key"
DETAIL: Key (user_id, look_id)=(140, 263008) already exists.
'INSERT INTO feedbacks (user_id, look_id, score) VALUES (%(user_id)s, %(look_id)s, %(score)s) RETURNING feedbacks.id' {'user_id': 140, 'score': 1, 'look_id': 263008}
IntegrityError (IntegrityError) duplicate key value violates unique constraint "feedbacks_user_id_look_id_key"
...
(there's about 24 of these integrity errors here)
...
DETAIL: Key (user_id, look_id)=(173, 263008) already exists.
'INSERT INTO feedbacks (user_id, look_id, score) VALUES (%(user_id)s, %(look_id)s, %(score)s) RETURNING feedbacks.id' {'user_id': 173, 'score': 1, 'look_id': 263008}
No handlers could be found for logger "sqlalchemy.pool.QueuePool"
Traceback (most recent call last):
File "load.py", line 40, in <module>
load_crawl_data_into_feedback()
File "load.py", line 21, in load_crawl_data_into_feedback
for comment in session.query(Comment).filter(Comment.type == Comment.TYPE_LOOK).yield_per(100):
File "/Volumes/Data2/Dropbox/projects/Giordano/venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2337, in instances
fetch = cursor.fetchmany(self._yield_per)
File "/Volumes/Data2/Dropbox/projects/Giordano/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 3230, in fetchmany
self.cursor, self.context)
File "/Volumes/Data2/Dropbox/projects/Giordano/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 3223, in fetchmany
l = self.process_rows(self._fetchmany_impl(size))
File "/Volumes/Data2/Dropbox/projects/Giordano/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 3343, in _fetchmany_impl
row = self._fetchone_impl()
File "/Volumes/Data2/Dropbox/projects/Giordano/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 3333, in _fetchone_impl
self.__buffer_rows()
File "/Volumes/Data2/Dropbox/projects/Giordano/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 3326, in __buffer_rows
self.__rowbuffer = collections.deque(self.cursor.fetchmany(size))
sqlalchemy.exc.ProgrammingError: (ProgrammingError) named cursor isn't valid anymore None None
在你得出关于由yield_per引起的这个错误的结论之前,我可以向你保证yield_per不是这里的罪魁祸首。
我尝试了相同的代码而没有唯一的约束,我根本没有遇到任何错误。
我认为完整性错误导致无法找到记录器“sqlalchemy.pool.QueuePool”的处理程序。
我假设每个完整性错误都会杀死队列池中的每个“线程”。
有人可以告诉我发生了什么事吗?
如果此时我对数据做不了多少,你会建议我做什么?
答案 0 :(得分:4)
该错误仅来自Python logging
模块;您的池类正在尝试记录某些调试消息,但您没有配置SQLA日志记录。 Configuring logging很容易,然后你可以看到它实际上想说的是什么。
我不是相当确定这里发生了什么,但这肯定无法帮助你回滚几十次顶级交易。回滚结束事务并使每个实时行对象无效。这当然不会与yield_per
很好地互动。
如果您的数据库支持保存点或嵌套事务(即Postgres或Oracle ...或可能最近的MySQL?),请尝试为每次尝试启动嵌套事务:
for comment in session.query(Comment).filter(Comment.type == Comment.TYPE_LOOK).yield_per(100):
try:
with session.begin_nested():
feedback = Feedback()
feedback.user_id = User.get_or_create(comment.model_id).id
feedback.look_id = comment.commentable_id
session.add(feedback)
session.flush()
except IntegrityError, e:
print "IntegrityError", e
session.commit()
with
回滚错误并提交成功,因此失败的flush
不会对主要交易的其余部分造成严重破坏。
如果您没有后端支持,脑海中浮现的其他明智选择是:
复杂化您的查询:在您的反馈表中添加LEFT JOIN
,以便在应用内了解反馈行是否已存在。
如果您愿意将(user_id, look_id)
作为主键,我认为您可以使用session.merge(feedback)
。这类似于基于主键的插入或更新:如果SQLA可以找到具有相同pk的现有行,它将更新它,否则它将在数据库中创建一个新行。但是,风险会为每一个新行触发额外的SELECT
。
答案 1 :(得分:4)
“在你得出关于由yield_per引起的这个错误的结论之前,我可以向你保证yield_per不是这里的罪魁祸首。”
我不确定你为什么认为 - yield_per()在这里非常重要,只需尝试使用相同的测试而不用 yield_per()来查看如果行为不同。通过使用yield_per(),psycopg2游标在该循环继续时保持打开状态。但是你通过session.rollback()
在psycopg2连接上发出了ROLLBACK。这确实会导致出现“命名游标不再有效”等错误。事实上, 命名游标的唯一原因是因为这就是你用psycopg2做服务器端游标的方法,这是yield_per()启用的一部分。
“我尝试了相同的代码而没有唯一的约束,我根本没有遇到任何错误。”
这是因为没有约束,不会抛出任何异常,并且不会命中rollback()。