Two Scoops of Django 1.8 学习笔记三

7. 查询和数据库层

7.1 对单个对象使用get_object_or_404()

在详情页面视图中,如果你想接受单个对象,用get_object_or_404()代替get()
警告:只在视图中使用!!!只在视图中使用!!!只在视图中使用!!!

7.2 谨慎的使用查询,它可能抛出异常

当你通过get_object_or_404()这个快捷方式得到Model的实例的时候,不需要用try-except封装,
因为get_object_or_404()已经帮你封装好了。
然而,在其他大多数情况下,你需要使用try-except。一些tips:

7.2.1 ObjectDoesNotExist vs. DoesNotExist

ObjectDoesNotExist允许被使用在任何model对象中,但是DoesNotExist只能在一些特定的model中使用

from django.core.exceptions import ObjectDoesNotExist

from flavors.models import Flavor
from store.exceptions import OutOfStock

def list_flavor_line_item(sku):
    try:
        return Flavor.objects.get(sku=sku, quantity__gt=0)
    except Flavor.DoesNotExist:
        msg = "We are out of {0}".format(sku)
        raise OutOfStock(msg)

def list_any_line_item(model, sku):
    try:
        return model.objects.get(sku=sku, quantity__gt=0)
    except ObjectDoesNotExist:
        msg = "We are out of {0}".format(sku)
        raise OutOfStock(msg)

7.2.2 当你只是想要一个对象,但是得到了多个

如果你的查询可能会返回多个对象,用MultipleObjectsReturned来检查

from flavors.models import Flavor
from store.exceptions import OutOfStock, CorruptedDatabase

def list_flavor_line_item(sku):
    try:
        return Flavor.objects.get(sku=sku, quantity__gt=0)
    except Flavor.DoesNotExist:
        msg = "We are out of {}".format(sku)
        raise OutOfStock(msg)
    except Flavor.MultipleObjectsReturned:
        msg = "Multiple items have SKU {}. Please fix!".format(sku)
        raise CorruptedDatabase(msg)

7.3 使用lazy evaluation 让查询更清晰

Django 的ORM 非常强大,它让代码清晰易读,利于维护。
通过lazy evaluation, 意味着在数据真正被使用之前,ORM不会调用SQL。
我们可以尽可能多的连接ORM方法和函数,直到拿到结果,django都不会接触到数据库层。

7.4 学习高级的查询工具

ORM 容易学,直观,涵盖了许多用例,然而还有若干件事情不能做好。在查询集返回后,我们会在python里处理更多的数据。
这是耻辱,因为每一个数据库的管理和传输都比python(或ruby, javascript, go, java等等这些语言)要快。
取代用python管理数据,我们通常会使用django的高级查询工具。这样做,我们不仅提高了性能,
而且比起我们自己创建的基于python的解决办法的代码,我们更喜欢使用已经被测试过的代码。

7.4.1 查询表达式

当在数据库上执行读取时,查询表达式被使用在创建值或计算。下面这个例子中,列出所有平均订单数大于一次的客户

# Bad Example
from models.customers import Customer


customers = []
for customer in Customer.objects.iterate():
    if customer.scoop_ordered > customer.srore_visits:
        customers.append(customer)

这个例子让我们不寒而栗,为什么?

  • 它用python来一个个遍历数据库中所有的顾客记录,这太慢了而且还占用内存
  • 在使用的情况下,它都可能产生竞态(race condition),当我们运行这段代码时,
    如果在别的地方,恰好customers正在与数据库进行交互,不是READ,而是UPDATE,那么这段代码的遍历会损失掉一些数据

幸运的是,django提供了一个更有效的,解决竞态(race condition)问题的方法

from django.db.models import F

from models.customers import Customer

customers = Customer.objects.filter(scoops_ordered__gt=F('store_visits'))

F()这个方法可以让数据库它自己来执行比较,类似下面的SQL语句:

SELECT * from customers_customer where scoops_ordered > store_visits

7.4.2 数据库函数

UPPER(), LOWER(), COALESCE(), CONCAT(), LENGTH(), SUBSTR()

https://docs.djangoproject.com/en/1.8/ref/models/database-functions/

7.5 不要抛弃原始的SQL语句,除非有必要

大多数情况下我们都会使用ORM来进行数据管理和操作,
那么什么时候该使用SQL语句呢,如果使用SQL语句能大大的简化python代码,不要犹豫,用吧。
比如说你连结了若干个查询集操作,而且每个操作都是在庞大的数据下进行的,用SQL语句也许会更有效。

7.6 根据需要加上索引

给任何model字段加上db_index=True都是容易的,但是判断该什么时候使用的话就不是那么容易了。
我们更倾向于开始的时候不加索引,根据需要再加
什么时候考虑加上索引:

  • 这个索引使用频繁,占据了所有查询的10-20%
  • 有真实数据,或者接近真实数据,因此我们可以分析出索引的结果
  • 我们能通过跑测试来判断这个索引是否产生了改善的结果

7.7 事务(Transactions)

django1.8的ORM默认行为是当查询调用时自动提交,在数据改变的情况下,这意味着每时每刻a.create() 或者 update()都在被调用,
它立刻改变了数据库里的数据。它的优势是让初期开发者更好的理解ORM,
劣势是如果一个视图需要存在2个或者多个数据更改,而只有一个更改成功了,那么这个数据可能有崩溃的风险

解决这个风险的办法是通过使用数据库事务,
一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:

  1. 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
  2. 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
    当事务被提交给了DBMS(数据库管理系统),则DBMS(数据库管理系统)需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为ACID特性。

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

django提供了一个强大的,相当易用的事务机制,在相对直观的模式中使用装饰器和上下文管理器保障了项目中数据的完整性。

7.7.1 在一个事务中封装每个http request

# settings/base.py

DATABASES = {
    'default': {
        # ...
        'ATOMIC_REQUESTS': True,
    },
}

django中通过设置ATOMIC_REQUESTS 让处理事务里的所有web请求变得更容易。通过设置’ATOMIC_REQUESTS’: True,
所有的requests都被封装在了事务中,包括只读数据。这种方法的优势是保证了安全性:所有在视图中的数据查询都被保护起来了,
劣势是性能受到了影响。我们无法告诉你这会影响多少性能,因为它依赖独立的数据库设计和不同的数据库引擎

当用ATOMIC_REQUESTS的时候,还有一件事情需要牢记,出现错误的时候只有数据库的状态会回滚。
发出一封确认邮件,然后封装的request事务回滚了,出现这种情况会令人相当尴尬。这个问题可能会发生在数据库以外的任何的写入过程中:
发送邮件,短信服务,调用第三方API,写入系统文件等等。然而,当写一个能create/update/delete的views的时候,而且它不与数据库条目进行交互,
你应该给view加上transaction.non_atomic_requests()装饰器

# flavors/views.py

from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone

from .models import Flavor

@transaction.non_atomic_requests
def posting_flavor_status(request, pk, status):
    flavor = get_object_or_404(Flavor, pk=pk)

    # This will execute in autocommit mode (Django's default).
    flavor.latest_status_change_attempt = timezone.now()
    flavor.save()

    with transaction.atomic():
        # This code executes inside a transaction.
        flavor.status = status
        flavor.latest_status_change_success = timezone.now()
        flavor.save()
        return HttpResponse("Hooray")

    # If the transaction fails, return the appropriate status
    return HttpResponse("Sadness", status_code=400)

7.7.2 明确的事务声明

明确的事务声明是给站点增加性能的方式之一,换句话说,就是指定哪个视图和业务逻辑封装在事务中,哪个不封装。
当谈到事务的时候,下面是一些好的准则:

  • 不修改数据的数据库操作不该被封装在事务中
  • 修改数据的数据库操作应该被封装在事务中
  • 包含数据库的修改的特殊情况,需要数据库的读取和性能方面的考虑可能会影响到前两个准则

如果还是不太清楚的话,下面这张表可以很好的解释:

目的 ORM方法 通常情况下使用事务
创建数据 .create(), .bulk_create(), .get_or_create() 可以
接收数据 .get(), .filter(), .count(), .iterate(), .exists(), .exclude(), .in_bulk, 等等
修改数据 .update() 可以
删除数据 .delete() 可以

7.7.3 django.http.StreamingHttpResponse and Transactions

如果视图函数正在返回一个django.http.StreamingHttpResponse,一旦response发生,它不可能去处理事务错误。如果你的项目中用到了这个response方法,那么ATOMIC_REQUESTS应该遵循下面其中一个:

  • 把ATOMIC_REQUESTS设置成默认的False,然后明确事务声明,或者…
  • 在django.db.transaction.non_atomic_requests装饰器中封装view

7.7.4 MySQL中的事务处理

MyISAM不支持事务处理,InnoDB支持

7.7.5 关于Django ORM 事务处理的资料