如何修复使用 Python ORM 工具 SQLAlchemy 时的常见陷阱

在使用 SQLAlchemy 时,那些看似很小的选择可能对这种对象关系映射工具包的性能产生重要影响。

对象关系映射Object-relational mapping(ORM)使应用程序开发人员的工作更轻松,在很大程度是因为它允许你使用你可能知道的语言(例如 Python)与数据库交互,而不是使用原始 SQL 语句查询。

SQLAlchemy是一个 Python ORM 工具包,它提供使用 Python 访问 SQL 数据库的功能。它是一个成熟的 ORM 工具,增加了模型关系、强大的查询构造范式、简单的序列化等优点。然而,它的易用性使得人们很容易忘记其背后发生了什么。使用 SQLAlchemy 时做出的看似很小的选择可能产生非常大的性能影响。

本文解释了开发人员在使用 SQLAlchemy 时遇到的一些最重要的性能问题,以及如何解决这些问题。

只需要计数但检索整个结果集

有时开发人员只需要一个结果计数,但是没有使用数据库计数功能,而是获取了所有结果,然后使用 Python 中的 len 完成计数。

count = len(User.query.filter_by(acct_active=True).all())

相反,使用 SQLAlchemy 的 count 方法将在服务器端执行计数,从而减少发送到客户端的数据。在前面的例子中调用 all() 也会导致模型对象的实例化,如果有很多数据,那么时间代价可能会非常昂贵。

除非还需要做其他的事情,否则只需使用 count 方法:

count = User.query.filter_by(acct_active=True).count()

只需要几列时检索整个模型

在许多情况下,发出查询时只需要几列数据。SQLAlchemy 可以只获取你想要的列,而不是返回整个模型实例。这不仅减少了发送的数据量,还避免了实例化整个对象。使用列数据的元组而不是模型可以快得多。

result = User.query.all()
for user in result:
  print(user.name, user.email)

反之,使用 with_entities 方法只选择所需要的内容:

result = User.query.with_entities(User.name, User.email).all()
for (username, email) in result:
  print(username, email)

每次循环都更新一个对象

避免使用循环来单独更新集合。虽然数据库可以非常快地执行单个更新,但应用程序和数据库服务器之间的往返时间将快速累加。通常,在合理的情况下争取更少的查询。

for user in users_to_update:
 user.acct_active = True
 db.session.add(user)

改用批量更新方法:

query = User.query.filter(user.id.in_([user.id for user in users_to_update]))
query.update({"acct_active": True}, synchronize_session=False)

触发级联删除

ORM 允许在模型关系上进行简单的配置,但是有一些微妙的行为可能会令人吃惊。大多数数据库通过外键和各种级联选项维护关系完整性。SQLAlchemy 允许你使用外键和级联选项定义模型,但是 ORM 具有自己的级联逻辑,可以取代数据库。

考虑以下模型:

class Artist(Base):
  __tablename__ = "artist"
  id = Column(Integer, primary_key=True)
  songs = relationship("Song", cascade="all, delete")
class Song(Base):
  __tablename__ = "song"
  id = Column(Integer, primary_key=True)
  artist_id = Column(Integer, ForeignKey("artist.id", ondelete="CASCADE"))

删除歌手将导致 ORM 在 song 表上发出 delete 查询,从而防止由于外键导致的删除操作。这种行为可能会成为复杂关系和大量记录的瓶颈。

请包含 passive_deletes 选项,以确保让数据库来管理关系。但是,请确保你的数据库具有此功能。例如,SQLite 默认情况下不管理外键。

songs = relationship("Song", cascade  all, delete", passive_deletes=True)

当要使用贪婪加载时,应使用延迟加载

延迟加载是 SQLAlchemy 处理关系的默认方法。从上一个例子构建来看,加载一个歌手时不会同时加载他或她的歌曲。这通常是一个好主意,但是如果总是需要加载某些关系,单独的查询可能会造成浪费。

如果允许以延迟方式加载关系,像 Marshmallow 这样流行的序列化框架可以触发级联查询。

有几种方法可以控制此行为。最简单的方法是通过 relationship 函数本身。

songs = relationship("Song", lazy="joined", cascade="all, delete")
这将导致一个左连接被添加到任何歌手的查询中,因此,songs 集合将立即可用。尽管有更多数据返回给客户端,但往返次数可能会少得多。

SQLAlchemy 为无法采用这种综合方法的情况提供了更细粒度的控制,可以使用 joinedload() 函数在每个查询的基础上切换连接的加载。

from sqlalchemy.orm import joinedload
artists = Artist.query.options(joinedload(Artist.songs))
print(artists.songs) # Does not incur a roundtrip to load

使用 ORM 进行批量记录导入

导入成千上万条记录时,构建完整模型实例的开销会成为主要瓶颈。想象一下,从一个文件中加载数千首歌曲记录,其中每首歌曲都先被转换为字典。

for song in songs:
  db.session.add(Song(`song))

相反,绕过 ORM,只使用核心的 SQLAlchemy 参数绑定功能。

batch = []
insert_stmt = Song.__table__.insert()
for song in songs:
  if len(batch) > 1000:
    db.session.execute(insert_stmt, batch)
    batch.clear()
  batch.append(song)
if batch:
  db.session.execute(insert_stmt, batch)

请记住,此方法会自然而然地跳过你可能依赖的任何客户端 ORM 逻辑,例如基于 Python 的列默认值。尽管此方法比将对象加载为完整的模型实例要快,但是你的数据库可能具有更快的批量加载方法。例如,PostgreSQL 的 COPY 命令为加载大量记录提供了最佳性能。

过早调用提交或刷新

在很多情况下,你需要将子记录与其父记录相关联,反之亦然。一种显然的方法是刷新会话,以便为有问题的记录分配一个 ID。

artist = Artist(name="Bob Dylan")
song = Song(title="Mr. Tambourine Man")
db.session.add(artist)
db.session.flush()
song.artist_id = artist.id

对于每个请求,多次提交或刷新通常是不必要的,也是不可取的。数据库刷新涉及强制在数据库服务器上进行磁盘写入,在大多数情况下,客户端将阻塞,直到服务器确认已写入数据为止。

SQLAlchemy 可以在幕后跟踪关系和管理相关键。

artist = Artist(name="Bob Dylan")
song = Song(title="Mr. Tambourine Man")
artist.songs.append(song)

总结

我希望这一系列常见的陷阱可以帮助你避免这些问题,并使你的应用平稳运行。通常,在诊断性能问题时,测量是关键。大多数数据库都提供性能诊断功能,可以帮助你定位问题,例如 PostgreSQL 的 pg_stat_statements 模块。

(0)

相关推荐

  • Python使用pip安装报错:is not a supported wheel on this platform的解决方法

    本文讲述了Python使用pip安装报错:is not a supported wheel on this platform的解决方法.分享给大家供大家参考,具体如下: 可能的原因1:安装的不是对应python版本的库,下载的库名中cp27代表python2.7,其它同理. 可能的原因2:这个是我遇到的情况(下载的是对应版本的库,然后仍然提示不支持当前平台) 在https://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy中,我下载到的numpy库文件名: n

  • python处理multipart/form-data的请求方法

    方法1: import requests url = "http://www.xxxx.net/login" #参数拼凑,附件上传格式如picurl参数,其他表单参数值拼成tuple格式: 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype), 4-tuples (filename, fileobj, contentype, custom_headers) files = {"userna

  • Python中用format函数格式化字符串的用法

    自python2.6开始,新增了一种格式化字符串的函数str.format(),可谓威力十足.那么,他跟之前的%型格式化字符串相比,有什么优越的存在呢?让我们来揭开它羞答答的面纱. 语法 它通过{}和:来代替%. "映射"示例 通过位置 In [1]: '{0},{1}'.format('kzc',18) Out[1]: 'kzc,18' In [2]: '{},{}'.format('kzc',18) Out[2]: 'kzc,18' In [3]: '{1},{0},{1}'.fo

  • Python的Django框架中forms表单类的使用方法详解

    Form表单的功能 自动生成HTML表单元素 检查表单数据的合法性 如果验证错误,重新显示表单(数据不会重置) 数据类型转换(字符类型的数据转换成相应的Python类型) Form相关的对象包括 Widget:用来渲染成HTML元素的工具,如:forms.Textarea对应HTML中的<textarea>标签 Field:Form对象中的一个字段,如:EmailField表示email字段,如果这个字段不是有效的email格式,就会产生错误. Form:一系列Field对象的集合,负责验证和

  • 浅谈python str.format与制表符\t关于中文对齐的细节问题

    写了一个练手的爬虫...在输出的时候出现了让人很不愉♂悦的问题 像这样: 令人十分难受啊! #------------------------------------------ 在此之前先说一下python中的.format格式化输出 python2.6开始,可以使用str.format进行轻松的格式化, 如上可以看到,对变量的处理简洁灵活,此外对数字的各种位数处理也很到位 {:<x}的语法表示左对齐(>为右对齐,^为居中),少于x位自动补齐(默认为空格补齐) 这里值得注意的是,x也可以作为

  • Python发送form-data请求及拼接form-data内容的方法

    网上关于使用python 的发送multipart/form-data的方法,多半是采用 ulrlib2 的模拟post方法,如下: import urllib2 boundary='-------------------------7df3069603d6' data=[] data.append('--%s' % boundary) data.append('Content-Disposition: form-data; name="app_id"\r\n') data.appen

  • Python中字符串格式化str.format的详细介绍

    前言 Python 在 2.6 版本中新加了一个字符串格式化方法: str.format() .它的基本语法是通过 {} 和 : 来代替以前的 %.. 格式化时的占位符语法: replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}" "映射"规则 通过位置 str.format() 可以接受不限个参数,位置可以

  • Python中str.format()详解

    1. str.format 的引入 在 Python 中,我们可以使用 + 来连接字符串,在简单情况下这种方式能够很好的工作.但是当我们需要进行复杂的字符串连接时,如果依然使用 + 来完成,不仅会使代码变得晦涩难懂,还会让代码变得难以维护,此时这种方式就显得力不从心了. 例如,我们想打印这样一条记录: User:John has completed Action:payment at Time:13:30:00 如果使用加号实现,会是下面这种形式: print "User:" + us

  • Python matplotlib的使用并自定义colormap的方法

    0.前言 添加colormap的对象是灰度图,可以变成热量图,从而更加明显的发现一些规律,适用于一些雷达图像等 from PIL import Image # 将彩色图片转换成黑白图片 im=Image.open("./pic.jpg").convert('L') # 保存图片 im.save("image.jpg") 1.从灰色图片中读取数据,转换成colormap图 import matplotlib.pyplot as plt import matplotli

  • 如何修复使用 Python ORM 工具 SQLAlchemy 时的常见陷阱

    在使用 SQLAlchemy 时,那些看似很小的选择可能对这种对象关系映射工具包的性能产生重要影响. 对象关系映射Object-relational mapping(ORM)使应用程序开发人员的工作更轻松,在很大程度是因为它允许你使用你可能知道的语言(例如 Python)与数据库交互,而不是使用原始 SQL 语句查询. SQLAlchemy是一个 Python ORM 工具包,它提供使用 Python 访问 SQL 数据库的功能.它是一个成熟的 ORM 工具,增加了模型关系.强大的查询构造范式.

  • Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍

    1. 添加一个新对象 前面介绍了映射到实体表的映射类User,如果我们想将其持久化(Persist),那么就需要将这个由User类建立的对象实例添加到我们先前创建的Session会话实例中: 复制代码 代码如下: ed_user = User('ed', 'Ed Jones', 'edspassword')session.add(ed_user) 上面两段代码执行完后对象持久化了么?你或许会兴冲冲的跑去数据库里查看,结果却失望而归--数据库里什么都没有.为什么呢?因为SQLAlchemy采取的是

  • Python ORM框架SQLAlchemy学习笔记之安装和简单查询实例

    最近正好在寻求一种Python的数据库ORM (Object Relational Mapper),SQLAlchemy (项目主页)这个开源项目进入了我的视线,本来想尝试着使用Django的ORM模块的,无奈Django的模块联系比较紧密,没能单独分拆下来,一定程度上说明Django自成体系的生态系统在给我们带来快速便捷的开发环境的同时牺牲了组装的灵活性. 初次学习,也没实质感觉到SQLAlchemy的好处,不过看其介绍的很多大公司均采用该项目,而且其支持的数据库还是蛮丰富的,所以我觉得花点时

  • Python ORM框架SQLAlchemy学习笔记之映射类使用实例和Session会话介绍

    1. 创建映射类的实例(Instance) 前面介绍了如何将数据库实体表映射到Python类上,下面我们可以创建这个类的一个实例(Instance),我们还是以前一篇文章的User类为例,让我们创建User对象: 复制代码 代码如下: >>> ed_user = User('ed', 'Ed Jones', 'edspassword')>>> ed_user.name'ed'>>> ed_user.password'edspassword'>&g

  • Python ORM框架SQLAlchemy学习笔记之数据查询实例

    前期我们做了充足的准备工作,现在该是关键内容之一查询了,当然前面的文章中或多或少的穿插了些有关查询的东西,比如一个查询(Query)对象就是通过Session会话的query()方法获取的,需要注意的是这个方法的参数数目是可变的,也就是说我们可以传入任意多的参数数目,参数的类型可以是任意的类组合或者是类的名称,接下来我们的例子就说明了这一点,我们让Query对象加载了User实例. 复制代码 代码如下: >>> for instance in session.query(User).or

  • Python ORM框架SQLAlchemy学习笔记之关系映射实例

    昨天简单介绍了SQLAlchemy的使用,但是没有能够涉及其最精彩的ORM部分,今天我将简单说明一下,当然主要还是讲解官方文档的内容,由于是学习笔记,有可能存在精简或者自己理解的部分,不做权威依据. 当我们开始使用ORM,一种可配置的结构可以用于描述我们的数据库表,稍后我们定义的类将会被映射到这些表上.当然现代的SQLAlchemy(新版本SQLAlchemy,原文是modern SQLAlchemy)使用Declarative把这两件事一起做了,即允许我们把创建类和描述定义数据库表以及它们之间

  • Python流行ORM框架sqlalchemy的简单使用

    安装 http://docs.sqlalchemy.org 1.安装 #进入虚拟环境 #执行 ./python3 -m pip install import sqlalchemy print(sqlalchemy.__version__) # 1.1.15 我这里使用的版本是1.1.15 创建连接对象 http://docs.sqlalchemy.org/en/latest/orm/tutorial.html#connecting from sqlalchemy import create_en

  • Python ORM数据库框架Sqlalchemy的使用教程详解

    目录 概念和数据类型 安装 连接 创建数据库表类(模型) 生成数据库表 会话 增删改查 增 查 改 删 执行裸sql with关闭会话 sql建造者模式 封装的工具 数据库配置文件database.properties 工具 测试实体 验证代码 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据

  • python orm 框架中sqlalchemy用法实例详解

    本文实例讲述了python orm 框架中sqlalchemy用法.分享给大家供大家参考,具体如下: 一.ORM简介 1. ORM(Object-Relational Mapping,对象关系映射):作用是在关系型数据库和业务实体对象之间做一个映射. 2. ORM优点: 向开发者屏蔽了数据库的细节,使开发者无需与SQL语句打交道,提高了开发效率; 便于数据库的迁移,由于每种数据库的SQL语法有差别,基于Sql的数据访问层在更换数据库时通过需要花费时间调试SQL时间,而ORM提供了独立于SQL的接

  • Python流行ORM框架sqlalchemy安装与使用教程

    本文实例讲述了Python流行ORM框架sqlalchemy安装与使用.分享给大家供大家参考,具体如下: 安装 http://docs.sqlalchemy.org 1.安装 #进入虚拟环境 #执行 ./python3 -m pip install import sqlalchemy print(sqlalchemy.__version__) # 1.1.15 我这里使用的版本是1.1.15 创建连接对象 http://docs.sqlalchemy.org/en/latest/orm/tuto

随机推荐