第 12 章 RDBMS和ORM

RDBMS(Relational DataBase Management System,关系型数据库管理系统)和ORM(Object-Relational Mapping,对象关系映射)是一个不太讨好的题目,但是早晚都要处理。许多应用程序都需要存储某种形式的数据,而开发人员通常会选择使用关系型数据库。并且当开发人员选择使用关系型数据库时,它们通常会选择某种ORM库。

 注意

广告:个人专属 VPN,独立 IP,流量大,速度快,连接稳定,多机房切换,每月最低仅 10 美元

本章将不再过多以Python中心,请多多包含。这里只讨论关系型数据库,但是这里涉及的很多内容同样适用于其他类型的数据库。

RDBMS是关于将数据以普通表单的形式存储的,而SQL是关于如何处理关系代数的。二者结合就可以对数据进行存储,同时回答关于数据的问题。然而,在面向对象程序中使用ORM有许多常见的困难,统称为对象关系阻抗失配(object-relational impedance mismatch,http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch)。根本在于,关系型数据库和面向对象程序对数据有不同的表示方式,彼此之间不能很好地映射:不管怎么做,将SQL表映射到Python的类都无法得到最优的结果。

ORM应该使数据的访问更加容易,这些工具会抽象创建查询、生成SQL的过程,无需自己处理。但是,你迟早会发现有些想做的数据库操作是这个抽象层不允许的。为了更有效地利用数据库,必须对SQL和RDBMS有深入了解以便能直接写自己的查询而无需每件事都依赖抽象层。

但这不是说要完全避免用ORM。ORM库可以帮助快速建立应用模型的原型,有些甚至能提供非常有用的工具,如模式(schema)的升降级。重要的是要了解它并不能完全替代RDBMS。许多开发人员试图在它们选择的语言中解决问题而不使用他们的模型API,通常他们给出的方案却并不优雅。

设想一个用来记录消息的SQL表。它有一个名为id的列作为主键和一个用来存放消息的字符串列。

CREATE TABLE message (
 id serial PRIMARY KEY,
 content text
);

我们希望收到消息时避免重复记录,所以一个典型的开发人员会这么写:

if message_table.select_by_id(message.id):
  # We already have the message, it's a duplicate, ignore and raise
  raise DuplicateMessage(message)
else:
  # Insert the message
  message_table.insert(message)

这在大多数情况下肯定可行,但它有些主要的弊端。

 
  • 它实现了一个已经在SQL模式中定义了的约束,所以有点儿代码重复。
  • 执行了两次SQL查询,SQL查询的执行可能会时间很长而且需要与SQL服务器往返的通信,造成额外的延迟。
  • 没有考虑到在调用select_by_id之后程序代码insert之前,可能有其他人插入一个重复消息的可能性,这会引发程序抛出异常。

下面是一种更好的方式,但需要同RDBMS服务器合作而不是将其看作是单纯的存储。

try:
  # Insert the message
  message_table.insert(message)
except UniqueViolationError:
  # Duplicate
  raise DuplicateMessage(message)

这段代码以更有效的方式获得了同样的效果而且没有任何竞态条件(race condition)问题。这是一种非常简单的模式,而且和ORM完全没有冲突。这个问题在于开发人员将SQL数据库看作是单纯的存储并且在他们的控制器代码而不是他们的模型中重复他们已经(或者可能)在SQL中实现的约束。

将SQL后端看作是模型API是有效利用它的好方法。通过它本身的过程性语言编写简单的函数调用即可操作存储在RDBMS中的数据。

另外需要强调的一点是,ORM支持多种数据库后端。许多ORM库都将其作为一项功能来吹捧,但它实际上却是个陷阱,等待诱捕那些毫无防备的开发人员。没有任何ORM库能提供对所有RDBMS功能的抽象,所以你将不得不削减你的代码,只支持那些RDBMS最基本的功能(或者你容忍),而且将不能在不破坏抽象层的情况下使用任何RDBMS的高级功能。

有些在SQL中尚未标准化的简单得事情在使用ORM时处理起来会很痛苦,如处理时间戳操作。如果代码写成了与RDBMS无关的就更是如此。基于这一点,在选择适合你的应用程序的RDBMS时要更加仔细1

减轻ORM库的这个问题的可行办法就是像2.3节描述的那样对它们进行隔离。这种方法不仅可以在需要时轻松将ORM库切换到另一个,而且可以在发现查询的使用效率不高的地方对其进行优化,越过大多数ORM引用。

建立这种隔离的一种简单办法是只在应用的某一个模块中使用ORM,如myapp.storage。这种方法应该只在高度抽象的层面输出数据操作的函数和方法。ORM应该只在这个模块中使用。在此之后,就可以加入任何提供了相同API的模块以替换myapp.storag

最后,本节的目标不是要在是否使用ORM的辩论中做出选择,互联网上已经有大量的关于其优缺点的讨论。本节的重点在于帮你理解对SQL和RDBMS充分了解在应用程序中充分利用它们的潜力有多么重要。

Python中最常使用的(和有争议的事实标准)ORM库是SQLAlchemy([http://www. sqlalchemy.org/](http://www. sqlalchemy.org/))。它支持大量的不同后端并且对大多数通用操作都提供了抽象。模式升级可以通过第三方库完成,如alembic(https://pypi.python.org/pypi/alembic)。

有些框架,如Django(https://www.djangoproject.com/),提供了它们自己的ORM库。如果选择使用一个框架,那么使用内置的库是明智的选择,通常(显然)与外部ORM库相比,内置的库与框架集成得更好。

 警告

大多数框架依赖的MVC(Model View Controller)架构很容易被滥用。它们在它们的模型中直接实现ORM,但却没有足够的抽象,任何在视图(view)和控制器(controller)中使用的模型(model)都将被ORM直接使用。这是应该避免的。应该写包含ORM库的数据模型而不是组成它的数据模型,这能提供更好的可测试性和更好的隔离,也可以在需要时更容易地切换到另外一种存储技术上。