使用 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 高效更新数据库的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy的基础使用

PythonORM框架SQLAlchemy的使用

ORM框架SQLAlchemy学习(未整理完)

ORM框架之SQLAlchemy

SQLAlchemy

SQLAlchemy学习-1.环境准备与基础使用