模型层进阶相关

模型层进阶相关

选择合适的层级工作

要在对应的level(MVC) 做对应的事. 例如计算 count, 在最低的数据库 level 里是最快的 (如果只需要知道此记录是否存在的话,用 exists() 会更快).
但要 注意: queryset 是 lazy 的,所以有时候在 higher level (例如模板) 里控制 queryset 是否真的执行,说不定会更高效.

下面这段代码很好的解释了不同 level 的意思:

# QuerySet operation on the database
# fast, because that's what databases are good at
# 执行效率最快, 属于数据库层级
my_bicycles.count()

# counting Python objects
# slower, because it requires a database query anyway, and processing
# of the Python objects
# 很慢, 属于在Python对象的级别层次处理
len(my_bicycles)

# Django template filter
# slower still, because it will have to count them in Python anyway,
# and because of template language overheads
# 仍然很慢, 模板层的本质还是需要在Python层面上进行数据的处理
{{ my_bicycles|length }}

理解QuerySet对象

切片

QuerySet可以支持切片语法, 这等同于SQL的limit和offset. 但是它不支持负数索引

print(models.Book.objects.all()[:4])
print(models.Book.objects.all()[4:8])

(0.002) SELECT `app01_book`.`id`, `app01_book`.`name`, `app01_book`.`pub_time`, `app01_book`.`publish_id` FROM `app01_book` LIMIT 4;
(0.000) SELECT `app01_book`.`id`, `app01_book`.`name`, `app01_book`.`pub_time`, `app01_book`.`publish_id` FROM `app01_book` LIMIT 4 OFFSET 4; 

可迭代

QuerySet对象支持迭代.

for book in models.Book.objects.filter(pk__gt=5):
    print(book)

惰性查询

惰性查询是Queryset对象的一个比较重要的特性. 看下面这个例子

q = models.Book.objects.filter(name__startswith='西')
q = q.filter(pk__gt=5)
q = q.filter(pub_time__year=2018)

print(q)

这上面的例子看起来是对数据库进行了3次查询, 但是实际上只有执行打印的时候才真正查询数据库了. 创建查询集后只有我们需要获取具体的数据, 然后orm才会去数据库"请求"值给我们.

官方推荐写法:

q = models.Book.objects.filter(
    name__startswith='西'
).filter(
    pk__gt=5
).filter(
    pub_time__year=2018
)

那么什么才是后去具体数据的时机呢? 官方文档描述了下列几种情形.

  1. Iteration: ie. 对 Queryset 进行 For 循环的操作.
  2. slice: q = models.Book.objects.all()[5:10:2] 当指定了步长的切片才会马上去执行数据库查询.
  3. picling/caching
  4. repr/str
  5. len (Note: 如果你只想知道这个 queryset 结果的长度的话,最高效的还是在数据库的层级调用 count () 方法,也就是 sql 中的 COUNT ().)
  6. list()
  7. bool()

缓存机制

每个查询集都包含一个缓存来最小化对数据库的请求, 充分理解缓存的工作机制能帮助我们写出高效的代码.

当我们创建了一个新的查询集之后, 一旦发生了上面描述的7种情形, 就会在请求数据库之后, 可能生成cache(保存在查询集对象内),之后对相同的查询集做操作就不会重新去请求数据库获取数据了.

可以看看下面的结果

# 第一种方式
print([p.name for p in models.Publish.objects.all()])
print([p.addr for p in models.Publish.objects.all()])

# 第二种方式
q = models.Publish.objects.all()
print([p.name for p in q])
print([p.addr for p in q])

第一种方式实际是请求了两次数据库, QuerySet对象生成之后就直接弃用了, 缓存机制没有用上.

第二种方式只请求了一次数据库, 在第一次遍历QuerySet之后, 就将结果缓存起来了, 接下来就是对同一个QuerySet对象进行Python层面上的操作了.

会发生缓存的情形

[entry for entry in queryset]  # 遍历整个查询集
bool(queryset)                # 做布尔值运算
entry in queryset             # in运算
list(queryset)                # 转换成列表

特别要注意一下这些是不会发生缓存的.

q = models.Publish.objects.all()

print(q[2:])  # 做切片操作, 这里会查询数据库, 但不会将结果缓存到原来的查询集中
print(q[2])   # 做索引操作, 也会查询数据库, 也不会将结果缓存.

print(q)      # 这里单纯的打印不会发生缓存
print(q)

# values, values_list都不会发生缓存. 下面也会发生
print(q.values('name', 'addr'))
print(q.values('addr'))

查询优化

官方提供的几种优化策略

  • 利用 queryset lazy 的特性去优化代码,尽可能的减少连接数据库的次数.
  • 如果查出的 queryset 只用一次,可以使用 iterator () 去来防止占用太多的内存,
  • 尽可能把一些数据库层级的工作放到数据库,例如使用 filter/exclude, F, annotate, aggregate (可以理解为 groupby)
  • 一次性拿出所有你要的数据,不去取那些你不需要的数据.
    意思就是要巧用 select_related (), prefetch_related () 和 values_list (), values (), 例如如果只需要 id 字段的话,用 values_list (‘id‘, flat=True) 也能节约很多资源。或者使用 defer()only() 方法:不加载某个字段 (用到这个方法就要反思表设计的问题了) / 只加载某些字段.
  • 如果不用 select_related 的话,去取外键的属性就会连数据再去查找.
  • bulk (批量) 地去操作数据,比如 bulk_create
  • 查找一条数据时,尽量用有索引的字段去查询,O (1) 或 O (log n) 和 O (n) 差别还是很大的
  • count() 代替 len(queryset), 用 exists() 代替 if queryset:

下面再详细总结其中几种优化方式

select_related

对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用 select_related 来对QuerySet进行优化。

select_related 返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。

简单说,在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。

下面是它和普通查询的区别

# 普通查询
book = models.Book.objects.filter(pk=2).first()  # type: models.Book
print(book.publish.name)
SELECT
    `app01_book`.`id`,
    `app01_book`.`name`,
    `app01_book`.`pub_time`,
    `app01_book`.`publish_id`
FROM
    `app01_book`
WHERE
    `app01_book`.`id` = 2
ORDER BY
    `app01_book`.`id` ASC
    LIMIT 1;

SELECT
    `app01_publish`.`id`,
    `app01_publish`.`name`,
    `app01_publish`.`addr`,
    `app01_publish`.`pub_detail_id`
FROM
    `app01_publish`
WHERE
    `app01_publish`.`id` = 2;

上面的查询一共执行了两句sql语句.

使用select_related方法来执行查询的效率之比较.

books = models.Book.objects.filter(pk__lt=4).select_related('publish')

for book in books:
    print(book.publish.name)
SELECT
    `app01_book`.`id`,
    `app01_book`.`name`,
    `app01_book`.`pub_time`,
    `app01_book`.`publish_id`,
    `app01_publish`.`id`,
    `app01_publish`.`name`,
    `app01_publish`.`addr`,
    `app01_publish`.`pub_detail_id`
FROM
    `app01_book`
    INNER JOIN `app01_publish` ON ( `app01_book`.`publish_id` = `app01_publish`.`id` )
WHERE
    `app01_book`.`id` < 4;

由于使用了select_related提前将字段关联, 后面的跨表查询并没有继续操作数据库.

select_related还支持连接多个外键, 可以通过一个外键字段一直关联下去. 下面就是跨了3张表

books = models.Book.objects.filter(pk__lt=3).select_related('publish__pub_detail')

for book in books:
    print(book.publish.pub_detail.email)

小结:

  1. select_related主要针一对一和多对一关系进行优化。
  2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
  3. 可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。
  4. 没有指定的字段不会缓存,如果要访问的话Django会再次进行SQL查询。

对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。

prefetch_related

prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。

prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。

# 只查询了两次数据库
books = models.Book.objects.prefetch_related('authors')
for book in books:
    print(book.authors.all())

defer与only

only(*field): 返回一个对象, 只对括号内的字段属性做了查询优化

defer(*field): 返回一个对象, 对括号外的字段属性做了优化, 与only相反

上面依然可以获取优化之外的字段属性, 但是却需要进行数据库的查询获取.

books = models.Book.objects.only('name', 'pk')
books2 = models.Book.objects.values('name', 'pk')
books3 = models.Book.objects.defer('id')
print(books)
print(books2)
print(books3)
SELECT `app01_book`.`id`, `app01_book`.`name` FROM `app01_book` LIMIT 21;
SELECT `app01_book`.`name`, `app01_book`.`id` FROM `app01_book` LIMIT 21;
SELECT `app01_book`.`id`, `app01_book`.`name`, `app01_book`.`pub_time`, `app01_book`.`publish_id` FROM `app01_book` LIMIT 21;

从上面的执行sql语句可以上看出来, only和values执行的是一样的, 只是only返回的是列表套对象, 而values是列表套字典的形式. defer原理与only一样, 查询的是与only相反的数据. 所以如果只需要用到很少的数据, 又需要一个对象的形式, 就可以用到上面两个方法.

only, defer不能跨表优化, 就像下面这样, 有多少书, 就需要执行多少次数据库, 效率非常低下.

books = models.Book.objects.only('pk', 'publish')
for book in books:
    print(book.pk, book.publish.name)

事务优化

事务操作不仅能够保证数据的安全, 还有一个很有用过的作用就是, 可以通过事务隔离Django默认的autocommit, 来避免Django频繁的向数据库提交数据. 这也能够很好的提升性能.

在Django中开启事务的语法非常简单.

from django.db import transaction
with transaction.atomic():
    pass

批量操作

在QuerySet中有许多批量操作的方式, 例如delete update bulk_create...

这些批量操作对应于数据库层面的批量操作, 能够有效的防止批频繁请求数据库.

details = [models.PublishDetail(email=f'email{i}') for i in range(5)]
for d in details:
    d.save()

# 批量操作
models.PublishDetail.objects.bulk_create(details)

上面for循环5次, 需要请求数据库5次, 而bulk_create只需要请求数据库一次. 数据越多, 效率上的差距越明显.

原文地址:https://www.cnblogs.com/yscl/p/11609940.html

时间: 2024-08-27 16:27:27

模型层进阶相关的相关文章

Django基础五之django模型层(二)多表操作

目录 一 创建模型 关于db_column和verbose_name 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询(基于join实现的) 进阶练习(连续跨表) 五 聚合查询.分组查询.F查询和Q查询 查询练习 F查询与Q查询 F查询 Q查询 六 ORM执行原生sql语句(了解) 执行原生查询 直接执行自定义SQL 七 Python脚本中调用Django环境(django外部脚本使用models) 八 补充多个app配置models 本节目录 一 创建模型 表和表之间的关系

tp框架-----Model模型层

1.Model模型层是用来做什么的呢? 主要是用来做操作数据库访问的.也就说明TP框架自带了一种访问数据库的方式,使用的是Model模型. 2.Model模型怎样使用呢? 要使用Model模型层访问数据库的话,需要做配置,因为必须把数据库的一些参数配置好之后,才能连接,所以找到Config.php.    如何修改配置呢? 1)打开下图路径中的Convention.php文件 2)复制convention.php中的下图内容到Home/Conf/config.php中 3)修改配置,将自己的数据

DJango周总结二:模型层,单表,多表操作,连表操作,数据库操作,事务

django周复习二 1,模型层:  1单表操作:   13个必会操作总结    返回QuerySet对象的方法有    all()    filter()    exclude()    order_by()    reverse()    distinct()    特殊的QuerySet    values()       返回一个可迭代的字典序列    values_list() 返回一个可迭代的元祖序列    返回具体对象的    get()    first()    last() 

django之模型层(待补充)

模型层 1. ORM查询 所有代码都是在test.py文件中运行的 注意:我如果想在test.py文件中测试相关代码,那么必须要进行配置,不然会报以下的错误 django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, but settings are not configured. You must either define the environment variable D

57 Django模型层3与数据传输与Ajax

目录 一.orm查询优化 1.only与defer 2.select_related与prefatch_related 二.模型层choices参数 三.MTV与MVC模型 1.MVC 2.MTV 3.区别 四.Ajax简介 五.前后端传输数据编码格式 1. 不同的编码格式 2. 前端朝后端发送数据的请求方式 2.1 form表单发送数据编码格式 2.2 ajax发送数据的编码格式 六.序列化(drf会用到) 一.orm查询优化 能少走数据库就少走数据库. 1.only与defer only:取

django模型层 、 ORM查询

模型层 ORM查询 单表查询 前期准备工作需求: 如何只单独测试django中的某一个py文件 如何书写测试脚本 如何使用: 在任意一个py文件中书写以下代码 应用下的tests 或者自己新建一个 import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day53.settings") import django django.se

TP框架---Model模型层---做模型对象

TP框架----Model模型层---------------做模型对象 Model模型层是用来做什么的呢???? 主要是用来做操作数据库访问的. 也就说明TP框架自带了一种访问数据库的方式,使用的是Model模型. Model模型怎样使用呢??? 要使用Model模型层访问数据库的话,需要做配置,因为必须把数据库的一些参数配置好之后,才能连接,所以找到Config.php 配置文件,打开 这些是关于数据库的配置, 要把这些粘贴到config.php 进行修改. config.php配置文件(修

Django的模型层

Django模型层是Django框架自己定义的一套独特的ORM技术.使用django模型开发的首要任务就是定义模型类及其属性.每个模型类都可以被映射为数据库中的一个数据表,而类属性被映射数据字段,除此之外,数据库表的主键.外键.约束等也通过类属性完成定义. 1.模型定义,通过模型类中的Mata子类定义数据模型,比如数据库的表名.数据默认排序方式等. 2.普通字段类型,普通字段类型指模型类中除了外键关系外的数据字段属性. 3.常用字段参数,每个字段类型都有一些特定的HTML标签和表单验证参数, 4

模型层的生成

在[CodeSmith快速入门之三:数据库我来了]中,我们介绍了对数据库的基本访问,在本章将会带大家进行模型层的编写. 首先先要了解模型层(实体层.VO层)的组成,如下所示:public class 实体名{    私有字段声明;    构造函数;    公共属性;}注:--私有字段声明:一般是先声明主键,再是非主键字段,骆驼命名法(首字母小写,新单词首字母大写)--公共属性:一般是先声明主键,再是非主键属性,帕斯卡命名法(首字母大写,新单词首字母大写) 1.创建C#模板并保存,取名为Model