在使用SQLAlchemy时,那些看似很小的选择可能会对这个对象关系映射工具包的性能产生重大影响。对象关系映射(ORM)让应用程序开发人员的工作更轻松,这在很大程度上是因为它允许您使用您可能知道的语言(例如Python)与数据库交互,而不是使用原始SQL查询查询。SQLAlchemy是一个PythonORM工具包,它使用Python提供对SQL数据库的访问。它是一个成熟的ORM工具,添加了模型关系、强大的查询构造范例、简单的序列化等。然而,它的易用性让人很容易忘记幕后发生的事情。使用SQLAlchemy时看似很小的选择可能会对性能产生非常大的影响。本文解释了开发人员在使用SQLAlchemy时遇到的一些最重要的性能问题,以及如何解决这些问题。只需要计数但检索整个结果集有时开发人员只需要一个结果计数,但他们不使用数据库计数函数,而是获取所有结果,然后在Python中使用len来完成计数。count=len(User.query.filter_by(acct_active=True).all())相反,使用SQLAlchemy的count方法将在服务器端执行计数,减少发送到客户端的数据。在前面的示例中调用all()也会导致模型对象的实例化,如果有大量数据,这在时间上可能会非常昂贵。除非你需要做其他事情,否则只需要使用计数方法:发出查询时需要列数据。SQLAlchemy可以只获取您想要的列,而不是返回整个模型实例。这不仅减少了发送的数据量,还避免了实例化整个对象。使用列数据的元组而不是模型可以快得多。result=User.query.all()foruserinresult:print(user.name,user.email)相反,使用with_entities方法只选择需要的内容:result=User.query.with_entities(User.name,User.email).all()for(username,email)inresult:print(username,email)每个循环更新一个对象以避免使用循环单独更新集合。虽然数据库可以非常快速地执行单个更新,但应用程序和数据库服务器之间的往返时间会很快加起来。一般来说,在合理的情况下尽量减少查询。对于users_to_update中的用户:user.acct_active=Truedb.session.add(user)改用批量更新方法:query=User.query.filter(user.id.in_([user.idforuserinusers_to_update]))query.update({"acct_active":True},synchronize_session=False)触发级联删除ORM允许对模型关系进行简单配置,但有一些可能令人惊讶的细微行为。大多数数据库通过外键和各种级联选项维护关系完整性。SQLAlchemy允许您使用外键和级联选项定义模型,但ORM有自己的级联逻辑可以替代数据库。考虑以下模型:classArtist(Base):__tablename__="artist"id=Column(Integer,primary_key=True)songs=relationship("Song",cascade="all,delete")classSong(Base):__tablename__="song"id=Column(Integer,primary_key=True)artist_id=Column(Integer,ForeignKey("artist.id",ondelete="CASCADE"))删除艺术家将导致ORM对歌曲表发出删除查询,防止key引起的删除操作。这种行为可能成为复杂关系和大量记录的瓶颈。请包括passive_deletes选项以确保数据库管理关系。但是,请确保您的数据库具有此功能。例如,SQLite默认不管理外键。songs=relationship("Song",cascadeall,delete",passive_deletes=True)当要使用贪婪加载时,应该使用延迟加载延迟加载是SQLAlchemy处理关系的默认方式。从前面的示例构建,加载歌手确实不要同时加载他或她的歌曲。这通常是个好主意,但如果某些关系总是需要加载,单独查询可能会很浪费。如果允许延迟加载关系,流行的序列如Marshmallow优化框架可以触发级联查询。有几种方法可以控制这种行为。最简单的方法是通过关系函数本身。songs=relationship("Song",lazy="joined",cascade="all,delete")这将导致leftjoin被添加到任何歌手的查询中,所以歌曲集合将立即可用。虽然更多的数据将返回给客户端,但往返次数可能会少得多。SQLAlchemy为无法使用这种综合方法的情况提供更细粒度的控制,并且joinedload()函数可用于在每个查询的基础上切换连接加载。fromsqlalchemy.ormimportjoinedloadartists=Artist.query.options(joinedload(Artist.songs))print(artists.songs)#不产生往返加载使用ORM批量记录导入导入数万条记录时,构建完整模型实例的开销可能成为主要瓶颈。想象一下,从一个文件中加载数千首歌曲记录,其中每首歌曲首先被转换为字典。对于歌曲中的歌曲:db.session.add(Song(`song))绕过ORM并仅使用核心SQLAlchemy参数绑定功能。batch=[]insert_stmt=Song.__table__.insert()forsonginsongs:iflen(batch)>1000:db.session.execute(insert_stmt,batch)batch.clear()batch.append(song)ifbatch:db.session.execute(insert_stmt,batch)请记住,此方法自然会跳过您可能依赖的任何客户端ORM逻辑,例如基于Python的列默认值。虽然此方法比将对象作为完整模型实例加载更快,但您的数据库可能有更快的批量加载方法。例如,PostgreSQL的COPY命令为加载大量记录提供最佳性能。过早调用Commit或Refresh在许多情况下,您需要将子记录与其父记录相关联,反之亦然。一种明显的方法是刷新会话,以便为相关记录分配一个ID。artist=Artist(name="BobDylan")song=Song(title="Mr.TambourineMan")db.session.add(artist)db.session.flush()song.artist_id=artist.id每个请求,多次提交或刷新通常是不必要和不可取的。数据库刷新涉及在数据库服务器上强制写入磁盘,在大多数情况下,客户端将阻塞,直到服务器确认数据已写入。SQLAlchemy可以跟踪关系并在幕后管理相关键。artist=Artist(name="BobDylan")song=Song(title="Mr.TambourineMan")artist.songs.append(song)总结我希望这份常见陷阱列表能帮助您避免这些问题并使您的应用程序运行流畅。通常,测量是诊断性能问题的关键。大多数数据库都提供性能诊断,可以帮助你定位问题,比如PostgreSQL的pg_stat_statements模块。
