数据库相关补充

整合现有的数据库

自动生成模型

django可以按照现有的数据创建模型。

python manage.py inspectdb

通过重定向Unix标准输出流来保存文件:

python manage.py inspectdb > models.py

这个特性是一个快捷方式,并不是一个确定的模型生成器。详见inspectdb文档 

一旦你创建好了你的模型,把文件命名为models.py,然后把它放到你应用的Python包中。然后把应用添加到你的INSTALLED_APPS 设置中。

默认情况下,inspectdb创建未被管理的模型。这就是说,模型的Meta类中的managed = False告诉Django不要管理每个表的创建、修改和删除:

class Person(models.Model):
    id = models.IntegerField(primary_key=True)
    first_name = models.CharField(max_length=70)
    class Meta:
       managed = False
       db_table = 'CENSUS_PERSONS'

如果你希望Django管理表的生命周期,你需要把managed选项改为 True(或者简单地把它移除,因为True是默认值)

安装Django核心表

接下来,运行migrate命令来安装所有所需的额外的数据库记录,比如后台权限和内容类型:

$ python manage.py migrate

 

为模型提供初始数据

当你首次建立一个应用的时候,为你的数据库预先安装一些硬编码的数据,是很有用处的。 有几种方法可以让Django自动创建这些数据:你可以通过fixtures提供初始数据,或者提供一个包含初始数据的sql文件

通常来讲,使用fixtrue更加简洁,因为它是数据库无关的,而使用sql初始化更加灵活。

提供初始数据的fixtures

[{
    "model": "myapp.person",
    "pk": 1,
    "fields": {
      "first_name": "John",
      "last_name": "Lennon"
    }
  },
  {
    "model": "myapp.person",
    "pk": 2,
    "fields": {
      "first_name": "Paul",
      "last_name": "McCartney"
    }
  }
]

下面是它的YAML格式:

- model: myapp.person
  pk: 1
  fields:
    first_name: John
    last_name: Lennon
- model: myapp.person
  pk: 2
  fields:
    first_name: Paul
    last_name: McCartney

你可以把这些数据储存在你应用的fixtures目录中。

加载数据很简单:只要调用manage.py loaddata <fixturename>就好了,其中<fixturename>是你所创建的fixture文件的名字。每次你运行loaddata的时候,数据都会从fixture读出,并且重复加载进数据库。注意这意味着,如果你修改了fixtrue创建的某一行,然后再次运行了 loaddata,你的修改将会被抹掉。

 

Django在哪里寻找fixture文件

通常,Django 在每个应用的fixtures目录中寻找fixture文件。你可以设置FIXTURE_DIRS选项为一个额外目录的列表,Django会从里面寻找。

运行manage.py loaddata命令的时候,你也可以指定一个fixture文件的目录,它会覆盖默认设置中的目录。

 

数据库访问优化

理解查询集

 

理解缓存属性

和整个QuerySet的缓存相同,ORM对象的属性的结果也存在缓存,通常来说,不可调用属性会被缓存

>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # Blog object is retrieved at this point
>>> entry.blog   # cached version, no DB access

但是通常来讲,可调用的属性每一次都会访问数据库。

>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # query performed
>>> entry.authors.all()   # query performed again

使用with模板标签

要利用QuerySet的缓存行为,你或许需要使用with模板标签。

使用简单的名字缓存复杂的变量,使用一个昂贵的方法(访问数据库)多次时非常有用

{% with total=business.employees.count %}
    {{ total }} employee{{ total|pluralize }}
{% endwith %}

使用iterator()

当有很多对象时,QuerySet缓存占用大量内存。使用iterator

iterator()将直接读取结果,而不在QuerySet级别执行任何缓存(内部,默认迭代器调用iterator()并高速缓存返回值)。对于返回大量只需要访问一次的对象的QuerySet,这可以带来更好的性能和显着减少内存

在数据库中而不是Python中做数据库的工作

比如:

如果上面那些都不够用,你可以自己生成SQL语句

使用QuerySet.extra()

 extra()是一个移植性更差,但是功能更强的方法,它允许一些SQL语句显式添加到查询

使用原始的SQL

编写你自己的自定义SQL语句,来获取数据或者填充模型。使用django.db.connection.queries来了解Django为你编写了什么,以及从这里开始。

用唯一的被索引的列来检索独立对象

有两个原因在get()中,用带有unique或者db_index的列检索独立对象。首先,由于查询经过了数据库的索引,所以会更快。其次,如果很多对象匹配查询,查询会更慢一些;列上的唯一性约束确保这种情况永远不会发生。

所以,使用博客模型的例子

>>> entry = Entry.objects.get(id=10)

会快于:

>>> entry = Entry.object.get(headline="News Item Title")

因为id被数据库索引,而且是唯一的。

下面这样做会十分缓慢:

>>> entry = Entry.objects.get(headline__startswith="News")

首先, headline没有被索引,它会使查询变得很慢:

其次,这次查找并不确保返回唯一的对象。如果查询匹配到多于一个对象,它会在数据库中遍历和检索所有这些对象。如果记录中返回了成百上千个对象,代价是非常大的。如果数据库运行在分布式服务器上,网络开销和延迟也是一大因素,代价会是它们的组合

 

一次性检索你需要的任何东西

在不同的位置多次访问数据库,一次获取一个数据集,通常来说不如在一次查询中获取它们更高效。如果你在一个循环中执行查询,这尤其重要。有可能你会做很多次数据库查询,但只需要一次就够了。所以:

使用QuerySet.select_related()和prefetch_related()

充分了解并使用select_related()prefetch_related()

  • 在视图的代码中,
  • 以及在适当的管理器和默认管理器中。要意识到你的管理器什么时候被使用和不被使用;有时这很复杂,所以不要有任何假设

使用QuerySet.values()和values_list()

当你仅仅想要一个带有值的字典或者列表,并不需要使用ORM模型对象时,可以适当使用values()。对于在模板代码中替换模型对象,这样会非常有用 —— 只要字典中带有的属性和模板中使用的一致,就没问题

 

 

使用QuerySet.count()

...如果你想要获取大小,不要使用 len(queryset)。

使用QuerySet.exists()

...如果你想要知道是否存在至少一个结果,不要使用if queryset。

但是不要过度使用

假设Email模型有一个body属性,并且和User有多对多的关联,下面的的模板代码是最优的:

{% if display_inbox %}
  {% with emails=user.emails.all %}
    {% if emails %}
      <p>You have {{ emails|length }} email(s)</p>
      {% for email in emails %}
        <p>{{ email.body }}</p>
      {% endfor %}
    {% else %}
      <p>No messages today.</p>
    {% endif %}
  {% endwith %}
{% endif %}

这是因为:

  1. 因为查询集是延迟加载的,如果‘display_inbox’为False,不会查询数据库。
  2. 使用with意味着我们为了以后的使用,把user.emails.all储存在一个变量中,允许它的缓存被复用。
  3. {% if emails %}的那一行调用了QuerySet.__bool__(),它导致user.emails.all()查询在数据库上执行,并且至少在第一行以一个ORM对象的形式返回。如果没有任何结果,会返回False,反之为True。
  4. {{ emails|length }}调用了QuerySet.__len__()方法,填充了缓存的剩余部分,而且并没有执行另一次查询。
  5. for循环的迭代器访问了已经缓存的数据。

总之,这段代码做了零或一次查询。唯一一个慎重的优化就是with标签的使用。在任何位置使用QuerySet.exists()或者QuerySet.count()都会导致额外的查询

使用QuerySet.update()和delete()

通过QuerySet.update()使用批量的SQL UPDATE语句,而不是获取大量对象,设置一些值再单独保存。deletes类似

直接使用外键的值

如果你仅仅需要外键当中的一个值,要使用对象上你已经取得的外键的值,而不是获取整个关联对象再得到它的主键。例如,执行:

entry.blog_id

而不是:

entry.blog.id

不要使用不必要的排序

如果模型具有默认顺序(Meta.ordering), 但是并不需要它,通过查询集调用无参order_by() 移除

整体插入

创建对象时,尽可能使用bulk_create()来减少SQL查询的数量。例如:

Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])

...更优于:

Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")

ManyToManyFields:

my_band.members.add(me, my_friend)

更优于:

my_band.members.add(me)
my_band.members.add(my_friend)

转载于:https://my.oschina.net/acutesun/blog/1517892

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值