模型层-模型层进阶
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可以指定一个或多个 参数
,例如 select
, where
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
子句。
where
和tables
都接受字符串列表。所有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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人