模型层-模型层进阶

1 Django中开启事务

事务的定义:将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)。

事务ACID四个特性

1 原子性:不可分割的最小单位
2 一致性:跟原子性相辅相成
3 隔离性:多个事务间相互不干扰
4 持久性:事务一旦确认永久生效

事务的回滚 rollback; 事务的确认 commit。

django中开启事务:

# 事务
# 买一本书,在数据库层面要做的事儿
# 1. 创建一条订单数据
# 2. 去产品表 将卖出数+1, 库存数-1

from django.db.models import F
from django.db import transaction

try:
    with transaction.atomic():  # with上下文开启事务 在with代码块内书写的所有ORM操作都属于同一个事务 
    models.Order.objects.create(num="110110111", product_id=1, count=1)                    # 创建一条订单数据
    models.Product.objects.filter(id=1).update(kucun=F("kucun")-1, maichu=F("maichu")+1)   # 能执行成功
    # with上下文结束,事务就结束了,超出with代码块的都是其他操作
except Exception as e:
    print(e)

 

2 QuerySet对象

2.1可切片

使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMIT 和OFFSET 子句。

Entry.objects.all()[:5]      # (LIMIT 5)
Entry.objects.all()[5:10]    # (OFFSET 5 LIMIT 5)

不支持负的索引(例如Entry.objects.all()[-1])。通常,查询集 的切片返回一个新的查询集 —— 它不会执行查询。

2.2可迭代

articleList = models.Article.objects.all()

for article in articleList:
    print(article.title)

2.3惰性查询

查询集 是惰性执行的 —— 创建查询集不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集 需要求值时,Django 才会真正运行这个查询。即你仅仅只是书写了ORM语句,在后面根本没有用到该语句所查询出来的数据,那么ORM会自动识别,直接不执行。

queryResult = models.Article.objects.all() #  not hits database
 
print(queryResult) # hits database
 
for article in queryResult:
    print(article.title)    # hits database

一般来说,只有在“请求”查询集 的结果时才会到数据库中去获取它们。当你确实需要结果时,查询集 通过访问数据库来求值。

2.4缓存机制

每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。

在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集 的求值将重用缓存的结果。

请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:

print([a.title for a in models.Article.objects.all()])
print([a.create_time for a in models.Article.objects.all()])

这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存查询集并重新使用它:

queryResult = models.Article.objects.all()

print([a.title for a in queryResult])
print([a.create_time for a in queryResult])

何时查询集不会被缓存?

查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存, 如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。所以,这意味着使用切片或索引来限制查询集将不会填充缓存。

例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:

queryset = Entry.objects.all()

print queryset[5] # Queries the database
print queryset[5] # Queries the database again

然而,如果已经对全部查询集求值过,则将检查缓存:

queryset = Entry.objects.all()
[entry for entry in queryset] # Queries the database
print queryset[5] # Uses cache
print queryset[5] # Uses cache

下面是一些其它例子,它们会使得全部的查询集被求值并填充到缓存中:

[entry for entry in queryset]
bool(queryset)
entry in queryset
list(queryset)

注:简单地打印查询集不会填充缓存。

queryResult=models.Article.objects.all()
print(queryResult) #  hits database
print(queryResult) #  hits database

2.5 exists()与iterator()方法

exists:

简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据:

if queryResult.exists():
    #SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
    print("exists...")

iterator:

当queryset非常巨大时,cache会成为问题。处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。

objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:
    print(obj.title)
#BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs:
    print(obj.title)

当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。

总结:

queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能会造成额外的数据库查询。

2.6 bulk_create批量插入数据

你想要批量插入数据的时候 使用orm给你提供的bulk_create能够大大的减少操作时间。

手动插入1万条数据

# 每循环一次插入一条数据,操作数据库
for i in range(10000):
    models.Book.objects.create(title='第%s本书' %i)

# 再将所有的数据查询并展示到前端页面
book_queryset = models.Book.objects.all()

批量插入10万条数据

book_list = []

# 循环一次生成一个对象,把对象放入列表,并没有操作数据库
for i in range(100000):
    book_obj = models.Book(title='第%s本书' %i)
    book_list.append(book_obj)

# 操作数据库,一次性插入数据
models.Book.objects.bulk_create(book_list)

2.7 get_or_create

如果存在,则获取,否则,创建

obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '123','u_id': 2, 't_id': 2})

# defaults 指定创建时,其他字段的值

2.8 update_or_create

如果存在,则更新,否则,创建

obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '123','u_id': 2, 't_id': 1})

# defaults 指定创建时或更新时的其他字段

 

3 数据库查询优化

3.1 only与defer

res = models.Book.objects.all()
print(res)  # ORM惰性查询,要用数据了才会走数据库

# 想要获取书籍表中所有书的名字
res = models.Book.objects.all()
for d in res:
    print(d.'title') # 循环出每个数据,数据点它的字段,all方法包含了所有字段,都不需要重新走数据库
    
    
# 你给我实现获取到的是一个数据对象 然后点title就能够拿到书名 并且没有其他字段
res = models.Book.objects.only('title')  # only方法只包含括号内的字段
print(res)  # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>, <Book: 论语爆款>, <Book: 聊斋爆款>, <Book: 老子爆款>]>

for i in res:
print(i.title)  # 点击only括号内的字段 不会走数据库
print(i.price)  # 点击only括号内没有的字段 会重新走数据库查询,而all()方法不需要重新走数据库


res = models.Book.objects.defer('title')  # 对象除了没有title属性之外其他的都有
for i in res:
    print(i.price)
    
"""
defer与only刚好相反
    defer括号内放的字段不在查询出来的对象里面 查询该字段需要重新走数据
    而如果查询的是非括号内的字段 则不需要走数据库了
"""

3.2 select_related和prefetch_related

def select_related(self, *fields)
    性能相关:表之间进行join连表操作,一次性获取关联的数据。

    总结:
    1. select_related主要针一对一和多对一关系进行优化。
    2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。

例如:
res = models.Book.objects.all()
for i in res:
    print(i.publish.name)  # 拿到书籍对象,for循环查每本书的出版社名称,每循环一次就要走一次数据库查询
    
# 连表操作,内部SQL语句是INNER JOIN,括号内只能放外键字段一对多、一对一,多对多不行
# 放多个外键字段,连接多张表 select_related('外键1'__'外键2'__'外键3')
res = models.Book.objects.select_related('publish')  
for i in res:
    print(i.publish.name) # select_related内部直接先将book与publish连起来,然后一次性将大表里的所有数据封装给查询出来的对象,这个时候无论对象点击book的数据还是publish的数据都无需再走数据库查询
    
=============================================================================================================================
def prefetch_related(self, *lookups)
    性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。

    总结:
    1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
    2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。

# prefetch_related内部是子查询,将查询出来的所有数据全部封装给查询出来的对象,无论对象点击book的数据还是publish的数据都无需再走数据库查询
# 给你感觉像一次性搞定,但比select_related多一条sql语句
res = models.Book.objects.prefetch_related('publish')
for i in res:
    print(i.publish.name)
    
注意:如果表很大,连表操作会耗时很长,虽然查询步骤比子查询少一步,但效率可能会更低

 

4 extra

extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了 extra() QuerySet修改机制 — 它能在 QuerySet生成的SQL从句中注入新子句

extra可以指定一个或多个 参数,例如 selectwhere or tables. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做。

4.1参数之select

The select 参数可以让你在 SELECT 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。

queryResult = models.Article.objects.extra(select={'is_recent': "create_time > '2017-09-05'"})

结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05。

4.2参数之where / tables

您可以使用where定义显式SQL WHERE子句 - 也许执行非显式连接。您可以使用tables手动将表添加到SQL FROM子句。

wheretables都接受字符串列表。所有where参数均为“与”任何其他搜索条件。

举例来讲:

queryResult = models.Article.objects.extra(where=['nid in (1,3) OR title like "py%" ','nid>2'])
extra, 额外查询条件以及相关表,排序
            
    models.UserInfo.objects.filter(id__gt=1)
    models.UserInfo.objects.all() 
    # id name age ut_id
            
    models.UserInfo.objects.extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
    # a. 映射
    # select 
    # select_params=None
    # select 此处 from 表
                
    # b. 条件
    # where=None
    # params=None,
    # select * from 表 where 此处
                
    # c. 表
    # tables
    # select * from 表,此处
                    
    # d. 排序
    # order_by=None
    # select * from 表 order by 此处

# select和select_params是一组,where和params是一组,tables用来设置from哪个表
# Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
# Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
# Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
# Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
                
models.UserInfo.objects.extra(
    select={'newid':'select count(1) from app01_usertype where id>%s'},
    select_params=[1,],
    where = ['age>%s'],
    params=[18,],
    order_by=['-age'],
    tables=['app01_usertype']
                )

"""
select 
    app01_userinfo.id,
    (select count(1) from app01_usertype where id>1) as newid
from app01_userinfo,app01_usertype
where 
    app01_userinfo.age > 18
order by 
    app01_userinfo.age desc
"""
                
result = models.UserInfo.objects.filter(id__gt=1).extra(
    where=['app01_userinfo.id < %s'],
    params=[100,],
    tables=['app01_usertype'],
    order_by=['-app01_userinfo.id'],
    select={'uid':1,'sw':"select count(1) from app01_userinfo"}
    )
print(result.query)
# SELECT (1) AS "uid", (select count(1) from app01_userinfo) AS "sw", "app01_userinfo"."id", "app01_userinfo"."name", "app01_userinfo"."age", "app01_userinfo"."ut_id" FROM "app01_userinfo" , "app01_usertype" WHERE ("app01_userinfo"."id" > 1 AND (app01_userinfo.id < 100)) ORDER BY ("app01_userinfo".id) DESC
# 在对象中加入字段
ret=models.Author.objects.all().filter(nid__gt=1).extra(select={'n':'select count(*) from app01_book where nid>%s'},select_params=[1])
print(ret[0].n)
print(ret.query)
# 给字段重命名
ret=models.Author.objects.all().filter(author_detail__telephone=132234556).extra(select={'bb':"app01_authordatail.telephone"}).values('bb')
print(ret)
print(ret.query)

 4.3 执行原生SQL

from django.db import connection, connections

cursor = connection.cursor()  
# cursor = connections['default'].cursor()    
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone()
row = cursor.fetchall()
ret = models.Author.objects.raw('select * from app01_author where nid>1')
print(ret)
for i in ret:
    print(i)
print(ret.query)

# 会把book的字段放到author对象中
ret = models.Author.objects.raw('select * from app01_book where nid>1')
print(ret)
for i in ret:
    print(i.price)
    print(type(i))

 

5 choices参数 

数据库字段设计常见,在设计表时,针对某个字段存在多个可能性,我们该如何存储? 如:用户表中的性别、学历、工作经验、是否结婚、客户来源......

在MySQL中用到枚举 enum('mail', 'femail', 'others'),ORM中只要某个字段的可能性可以列举完全,一般情况下都会采用choices参数。

class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    # 先写对应关系
    gender_choices = (
        (1,'男'),
        (2,'女'),
        (3,'其他'),
    )
    # 字段类型参照前面的对应关系中小元祖的第一个元素,数据存储范围是由字段类型决定
    # 该gender字段存的还是数字,但是如果数字在上面元祖列举的范围之内,那么可以非常轻松的获取到数字对应的真正的内容
    # choicse参数等于写好的对应关系
    gender = models.IntegerField(choices=gender_choices)
    
    score_choices = (
        ('A','优秀'),
        ('B','良好'),
        ('C','及格'),
        ('D','不合格'),
    )
    # 保证字段类型跟列举出来的元祖第一个数据类型一致即可
    score = models.CharField(choices=score_choices,null=True)
    
    
# 插入数据   
from app01 import models

models.User.objects.create(username='jason',age=18,gender=1)
models.User.objects.create(username='egon',age=85,gender=2)
models.User.objects.create(username='tank',age=40,gender=3)
models.User.objects.create(username='tony',age=45,gender=4)  # 存的时候 没有列举出来的数字也能存(范围还是按照字段类型决定)

# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender)    
print(user_obj.get_gender_display())  # 只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()

user_obj = models.User.objects.filter(pk=4).first()
print(user_obj.get_gender_display())  # 如果没有对应关系 那么字段是什么还是展示什么,这里显示4

 

posted @ 2022-12-29 16:48  不会钓鱼的猫  阅读(27)  评论(0编辑  收藏  举报