花 5 分钟学习 wiki,然后进入实际的应用程序编程,Django 程序员很容易创建让人迷惑、难于维护或低效的模型类。在本文中,了解如何避免一些常见的查询错误、如何使用模型管理器来封装复杂查询以及如何充分利用 Django V1.1 强大的聚集特性。
在 Django 内,与数据库的大多数交互都通过对象关系映射器(ORM),这个特性是 Django 与其他最新的 Web 框架(比如 Rails)所共有的。ORM 越来越受开发人员欢迎,因为 ORM 能够自动化与数据库的很多常见交互,而且会使用为人熟知的面向对象方式,而不是 SQL 语句。
Django 程序员可能会选择绕过原生 ORM,而选择流行的 SQLAlchemy 包,虽然 SQLAlchemy 十分强大,但是却较难使用,而且需要更多的代码行。虽然有些 Django 应用程序是使用 SQLAlchemy 而非原生 ORM 开发的,但是 Django 最吸引人的一些特性,比如其自动生成的管理界面,都要求使用 ORM。
|
本文着重阐释了 Django ORM 的一些不为人熟知的特性,此外,本文还为 SQLAlchemy 的用户提供了一些有关低效查询生成的告诫,这对其编码很有帮助。
本文中使用的软件版本包括:
- Django V1.0.2(第 1 和第 2 部分)
- Django V1.1 alpha(第 3 部分)
- sqlite3
- Python V2.4-2.6(Django 尚不支持 Python V3)
- IPython(针对示例输出)
Django ORM 支持很多数据库后端,但 sqlite3 最易于安装,并且常常与操作系统捆绑。本文中的例子应该能与任何后端协作。要想获得 Django 支持的数据库的完整列表,请参见 参考资料。
避免 ORM 查询生成中的常见陷阱
Django 的设计支持敏捷开发的风格,因此能快速进行原型化和实验。在开始阶段,最好不要过于担心性能,而是要关注可读性和实现的简便性。
有时,发现性能问题并不需要太长时间。通常在初次用实际数据试用应用程序时,很容易发现性能问题。有时,若只包含几个测试的测试套件的执行时间超过了 5 分钟的界限,这就表明存在性能问题。有时,应用程序运行过慢,也表示性能问题的存在。所幸的是,现在已经有了一些很容易识别的模式,这些模式亦很容易修复。清单 1(应用程序的 models.py 文件)和 清单 2 给出了一个很常见的例子。
清单 1. 示例应用程序的基本模型:models.py
from django.db import models # Some kind of document, like a blog post or a wiki page class Document(models.Model): name = models.CharField(max_length=255) # A user-generated comment, such as found on a site like # Digg or Reddit class Comment(models.Model): document = models.ForeignKey(Document, related_name='comments') content = models.TextField() |
|
清单 2 显示了如何以一种低效的方式访问清单 1 中所设置的那些模型。
清单 2. 非常慢地访问那些模型
from examples.model import * import uuid # First create a lot of documents and assign them random names for i in range(0, 10000): Document.objects.create(name=str(uuid.uuid4())) # Get a selection of names back to be looked up later names = Document.objects.values_list('name', flat=True)[0:5000] # The really slow way to get back a list of Documents that # match these names documents = [] for name in names: documents.append(Document.objects.get(name=name)) |
这虽然是一个人为的示例,却展示了一种非常常见的用例:给定一列标识符,从数据库获得对应于这些标识符的所有项目。
当使用内存中的 sqlite3 时,上述示例代码的运行时间为 65 秒。如果是一个独立于文件系统的数据库,运行所花时间可能更长。不过,清单 3 中也有针对这个运行缓慢的查询的一个补丁。与针对每个名称值发出多个数据库查询相反,使用 fieldname__in 操作符来生成一个 SQL 查询,如下所示:
SELECT * FROM model WHERE fieldname IN ('1', '2', ...) |
(所生成的实际查询语法将会随数据库引擎而变化。)
清单 3. 用来获得条目列表的快速查询
from examples import models
import uuid
for i in range(0, 10000):
Document.objects.create(name=str(uuid.uuid4()))
names = Document.objects.values_list('name', flat=True)[0:5000]
documents = list(Document.objects.filter(name__in=names))
|
上述代码在 3 秒内即可执行。请注意此代码会将查询结果强制转型为一个列表,以强制对此查询求值。由于 Django 查询会被延迟求值,因此,简单的分配查询结果并不会引起对数据库的任何访问,亦使对比无效。
习惯于编写原始 SQL 的数据库大师们会觉得本例十分直白,但是很多 Python 程序员并不具有数据库背景。有时,程序员的开发习惯往往有悖于效率。清单 4 给出了改进清单 2 中的代码的一种可能方式,这种方式是程序员很有可能选择采用的,因为他们没有意识到这是个陷阱。
清单 4. 一个会降低数据库使用效率的常见模型
for name in names: documents.append(get_document_by_name(name)) def get_document_by_name(name): return Document.objects.get(name=name)) |
表面上看,创建一个用来从数据库检索文档的单独方法似乎是个不错的主意。但是这里还有其他一些工作要做,例如在返回前向模型中添加数据。请注意,对于这个模型,进行重构形成独立的方法看起来像是对代码的改进。在开发之初就编写单元测试并包括进一些针对大型数据集的测试可以帮助我们识别重构所导致的性能骤降。
用管理器模型封装常见查询
所有 Django 的开发人员都使用内置 Manager 类:表单 Model.objects.* 的所有方法,都会调用此类。这个基础 Manager 类自动可用,并且提供常用的一些能够返回 QuerySets 的方法(例如,all())、返回值的方法(例如,count())及返回 Model 实例的方法(例如, get_or_create())。
我们鼓励 Django 的开发人员覆盖这个基础 Manager 类。为了说明此特性的用处,我们对这个示例应用程序进行了扩展,为它添加了一个新模型 Format,这个模型描述了此系统内文档的格式。下面是一个示例。
清单 5. 为示例添加一个模型
from django.db import models
class Document(models.Model):
name = models.CharField(max_length=255)
format = models.ForeignKey('Format')
class Comment(models.Model):
document = models.ForeignKey(Document, related_name='comments')
content = models.TextField()
class Format(models.Model):
type = models.CharField(choices=( ('Text file', 'text'),
('ePub ebook', 'epub'),
('HTML file', 'html')),
max_length=10)
|