使用 SQLAlchemy ORM 高效更新数据库
Posted
技术标签:
【中文标题】使用 SQLAlchemy ORM 高效更新数据库【英文标题】:Efficiently updating database using SQLAlchemy ORM 【发布时间】:2010-09-21 05:11:31 【问题描述】:我正在启动一个新的应用程序并考虑使用 ORM——尤其是 SQLAlchemy。
假设我的数据库中有一个列“foo”,我想增加它。在直接的 sqlite 中,这很容易:
db = sqlite3.connect('mydata.sqlitedb')
cur = db.cursor()
cur.execute('update table stuff set foo = foo + 1')
我找到了等效的 SQLAlchemy SQL-builder:
engine = sqlalchemy.create_engine('sqlite:///mydata.sqlitedb')
md = sqlalchemy.MetaData(engine)
table = sqlalchemy.Table('stuff', md, autoload=True)
upd = table.update(values=table.c.foo:table.c.foo+1)
engine.execute(upd)
这会稍微慢一些,但内容不多。
这是我对 SQLAlchemy ORM 方法的最佳猜测:
# snip definition of Stuff class made using declarative_base
# snip creation of session object
for c in session.query(Stuff):
c.foo = c.foo + 1
session.flush()
session.commit()
这样做是正确的,但它需要的时间是其他两种方法的不到 50 倍。我想那是因为它必须先将所有数据带入内存才能使用它。
有没有什么方法可以使用 SQLAlchemy 的 ORM 生成高效的 SQL?或者使用任何其他python ORM?还是我应该回去手动编写 SQL?
【问题讨论】:
好吧,我假设答案是“这不是 ORM 做得好的”。那好吧;我生活和学习。 已经有一些实验在不同的 ORM 上运行,以及它们在负载和胁迫下的表现。手边没有链接,但值得一读。 最后一个(ORM)示例存在的另一个问题是它不是atomic。 【参考方案1】:没有经过测试,我会尝试:
for c in session.query(Stuff).all():
c.foo = c.foo+1
session.commit()
(IIRC,commit() 无需 flush() 即可工作)。
我发现有时执行大型查询然后在 python 中进行迭代可能比大量查询快 2 个数量级。我假设遍历查询对象的效率低于遍历查询对象的 all() 方法生成的列表。
[请注意下面的评论 - 这根本没有加快速度]。
【讨论】:
添加 .all() 和删除 .flush() 根本没有改变时间。【参考方案2】:如果是因为创建对象的开销,那么 SA 可能根本无法加速。
如果是因为它正在加载相关对象,那么您也许可以通过延迟加载来做一些事情。是否由于引用而创建了很多对象? (IE,获取 Company 对象也会获取所有相关的 People 对象)。
【讨论】:
不,桌子是靠自己的。我以前从未使用过 ORM——这只是他们不擅长的事情吗? 创建对象会产生开销,但在我看来这是值得的——能够在数据库中持久存储对象真是太棒了。【参考方案3】:SQLAlchemy 的 ORM 旨在与 SQL 层一起使用,而不是隐藏它。但是在同一事务中使用 ORM 和普通 SQL 时,您必须牢记一两件事。基本上,从一方面来看,ORM 数据修改只会在您从会话中刷新更改时才会影响数据库。另一方面,SQL 数据操作语句不会影响会话中的对象。
如果你说
for c in session.query(Stuff).all():
c.foo = c.foo+1
session.commit()
它会按照它说的去做,从数据库中获取所有对象,修改所有对象,然后在需要将更改刷新到数据库时,一一更新行。
您应该这样做:
session.execute(update(stuff_table, values=stuff_table.c.foo: stuff_table.c.foo + 1))
session.commit()
这将像您期望的那样作为一个查询执行,并且因为至少默认会话配置会在提交时使会话中的所有数据过期,所以您没有任何过时数据问题。
在即将发布的 0.5 系列中,您也可以使用此方法进行更新:
session.query(Stuff).update(Stuff.foo: Stuff.foo + 1)
session.commit()
这将基本上运行与之前的 sn-p 相同的 SQL 语句,但还会选择更改的行并使会话中的任何陈旧数据失效。如果您知道更新后没有使用任何会话数据,您还可以在更新语句中添加 synchronize_session=False
并删除该选择。
【讨论】:
第三种方式,会不会触发orm事件(比如after_update)? @Ken,不,不会。请参阅 Query.update docs.sqlalchemy.org/en/13/orm/… 的 API 文档。相反,您有一个 after_bulk_update docs.sqlalchemy.org/en/13/orm/… 的事件【参考方案4】:session.query(Clients).filter(Clients.id == client_id_list).update('status': status)
session.commit()
试试这个 =)
【讨论】:
这个方法对我有用。但问题是它的速度很慢。几条 10 万条数据记录需要很长时间。有没有更快的方法? 非常感谢这种方法对我有用。 sqlachemy 没有更短的方法来更新json
列,这真的很糟糕
对于那些在使用此方法时仍然存在性能问题的人:默认情况下,这可能会先对每条记录执行 SELECT,然后才更新。将 synchronize_session=False 传递给 update() 方法可以防止这种情况发生,但请确保只有在 commit() 之前不使用再次更新的对象时才这样做。
什么是Clients
?我在原始帖子或您的帖子中没有看到任何提及它,所以我不太确定这里发生了什么。【参考方案5】:
以下是如何解决相同问题而无需手动映射字段的示例:
from sqlalchemy import Column, ForeignKey, Integer, String, Date, DateTime, text, create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.attributes import InstrumentedAttribute
engine = create_engine('postgres://postgres@localhost:5432/database')
session = sessionmaker()
session.configure(bind=engine)
Base = declarative_base()
class Media(Base):
__tablename__ = 'media'
id = Column(Integer, primary_key=True)
title = Column(String, nullable=False)
slug = Column(String, nullable=False)
type = Column(String, nullable=False)
def update(self):
s = session()
mapped_values =
for item in Media.__dict__.iteritems():
field_name = item[0]
field_type = item[1]
is_column = isinstance(field_type, InstrumentedAttribute)
if is_column:
mapped_values[field_name] = getattr(self, field_name)
s.query(Media).filter(Media.id == self.id).update(mapped_values)
s.commit()
因此,要更新 Media 实例,您可以执行以下操作:
media = Media(id=123, title="Titular Line", slug="titular-line", type="movie")
media.update()
【讨论】:
【参考方案6】:有几种使用 sqlalchemy 进行更新的方法
1) for c in session.query(Stuff).all():
c.foo += 1
session.commit()
2) session.query(Stuff).update("foo": Stuff.foo + 1)
session.commit()
3) conn = engine.connect()
table = Stuff.__table__
stmt = table.update().values('foo': Stuff.foo + 'a')
conn.execute(stmt)
conn.commit()
【讨论】:
以上是关于使用 SQLAlchemy ORM 高效更新数据库的主要内容,如果未能解决你的问题,请参考以下文章