Django 与数据库交互,你需要知道的 9 个技巧

STDDEV_POP(EXTRACT(EPOCH FROM duration))

FROM

report;

avg | stddev_pop

----------------±-----------------

00:00:00.55432 | 1.06310113695549

(1 row)

那么我们如何在 Django 中实现呢?你猜到了 – 一个自定义函数:

common/db.py

from django.db.models import Func

class Epoch(Func):

function = ‘EXTRACT’

template = “%(function)s(‘epoch’ from %(expressions)s)”

我们的新函数这样使用:

from django.db.models import Avg, StdDev, F

from common.db import Epoch

Report.objects.aggregate(

avg_duration=Avg(‘duration’),

std_duration=StdDev(Epoch(F(‘duration’))),

)

{‘avg_duration’: datetime.timedelta(0, 0, 55432),

‘std_duration’: 1.06310113695549}

*注意在 Epoch 调用中使用 F 表达式。

4. 声明超时(Statement Timeout)


这可能是我给的最简单的也是最重要的提示。我们是人类,我们都会犯错。我们不可能考虑到每一个边缘情况,所以我们必须设定边界。

与其他非阻塞应用程序服务器(如 Tornado,asyncio 甚至 Node)不同,Django 通常使用同步工作进程。这意味着,当用户执行长时间运行的操作时,工作进程会被阻塞,完成之前,其他人无法使用它。

应该没有人真正在生产中只用一个工作进程来运行 Django,但是我们仍然希望确保一个查询不会浪费太多资源太久。

在大多数 Django 应用程序中,大部分时间都花在等待数据库查询上了。所以,在 SQL 查询上设置超时是一个很好的开始。

我喜欢像这样在我的 wsgi.py 文件中设置一个全局超时:

wsgi.py

from django.db.backends.signals import connection_created

from django.dispatch import receiver

@receiver(connection_created)

def setup_postgres(connection, **kwargs):

if connection.vendor != ‘postgresql’:

return

Timeout statements after 30 seconds.

with connection.cursor() as cursor:

cursor.execute(“”"

SET statement_timeout TO 30000;

“”")

为什么是 wsgi.py? 因为这样它只会影响工作进程,不会影响进程外的分析查询,cron 任务等。

希望您使用的是持久的数据库连接,这样每次请求都不会再有连接开销。超时也可以配置到用户粒度:

postgresql=#> alter user app_user set statement_timeout TO 30000;

ALTER ROLE

题外话:我们花了很多时间在其他常见的地方,比如网络。因此,请确保在调用远程服务时始终设置超时时间:

import requests

response = requests.get(

‘https://api.slow-as-hell.com’,

timeout=3000,

)

5. 限制(Limit)


这与设置边界的最后一点有些相关。有时我们的客户的一些行为是不可预知的。比如,同一用户打开另一个选项卡并在第一次尝试「卡住」时再试一次并不罕见。

这就是为什么需要使用限制(Limit)。

我们限制某一个查询的返回不超过 100 行数据:

bad example

data = list(Sale.objects.all())[:100]

这很糟糕,因为虽然只返回 100 行数据,但是其实你已经把所有的行都取出来放进了内存。

我们再试试:

data = Sale.objects.all()[:100]

这个好多了,Django 会在 SQL 中使用 limit 子句来获取 100 行数据。

我们增加了限制,但我们仍然有一个问题 – 用户想要所有的数据,但我们只给了他们 100 个,用户现在认为只有 100 个数据了。

并非盲目的返回前 100 行,我们先确认一下,如果超过 100 行(通常是过滤以后),我们会抛出一个异常:

LIMIT = 100

if Sales.objects.count() > LIMIT:

raise ExceededLimit(LIMIT)

return Sale.objects.all()[:LIMIT]

挺有用,但是我们增加了一个新的查询。

能不能做的更好呢?我们可以这样:

LIMIT = 100

data = Sale.objects.all()[:(LIMIT + 1)]

if len(data) > LIMIT:

raise ExceededLimit(LIMIT)

return data

我们不取 100 行,我们取 100 + 1 = 101 行,如果 101 行存在,那么我们知道超过了 100 行:

记住 LIMIT + 1 窍门,有时候它会非常方便。

6. 事务与锁的控制


这个比较难。由于数据库中的锁机制,我们开始在半夜发现事务超时错误。在我们的代码中操作事务的常见模式如下所示:

from django.db import transaction as db_transaction

with db_transaction.atomic():

transaction = (

Transaction.objects

.select_related(

‘user’,

‘product’,

‘product__category’,

)

.select_for_update()

.get(uid=uid)

)

事务操作通常会涉及用户和产品的一些属性,所以我们经常使用 select_related 来强制 join 并保存一些查询。

更新交易还会涉及获得一个锁来确保它不被别人获得。

现在,你看到问题了吗?没有?我也没有。(作者好萌)

我们有一些晚上运行的 ETL 进程,主要是在产品和用户表上做维护。这些 ETL 操作会更新字段然后插入表,这样它们也会获得了表的锁。

那么问题是什么?当 select_for_update 与 select_related 一起使用时,Django 将尝试获取查询中所有表的锁。

我们用来获取事务的代码尝试获取事务表、用户、产品、类别表的锁。一旦 ETL 在午夜锁定了后三个表,交易就开始失败。

一旦我们对问题有了更好的理解,我们就开始寻找只锁定必要表(事务表)的方法。(又)幸运的是,select_for_update 的一个新选项在 Django 2.0 中可用:

from django.db import transaction as db_transaction

with db_transaction.atomic():

transaction = (

Transaction.objects

.select_related(

‘user’,

‘product’,

‘product__category’,

)

.select_for_update(

of=(‘self’,)

)

.get(uid=uid)

)

这个 of 选项被添加到 select_for_update ,使用 of 可以指明我们要锁定的表,self 是一个特殊的关键字,表示我们要锁定我们正在处理的模型,即事务表。

目前,该功能仅适用于 PostgreSQL 和 Oracle。

7. 外键索引(FK Indexes)


创建模型时,Django 会在所有外键上创建一个 B-Tree 索引,它的开销可能相当大,而且有时候并不很必要。

典型的例子是 M2M(多对多)关系的直通模型:

class Membership(Model):

group = ForeignKey(Group)

user = ForeignKey(User)

在上面的模型中,Django 将会隐式的创建两个索引:一个用于用户,一个用于组。

M2M 模型中的另一个常见模式是在两个字段一起作为一个唯一约束。在这种情况下,意味着一个用户只能是同一个组的成员,还是那个模型:

class Membership(Model):

group = ForeignKey(Group)

user = ForeignKey(User)

class Meta:

unique_together = (

‘group’,

‘user’,

)

这个 unique_together 也会创建两个索引,所以我们得到了两个字段三个索引的模型 ?

根据我们用这个模型的职能,我们可以设置db_index=False忽略 FK 索引,只保留唯一约束索引:

class Membership(Model):

group = ForeignKey(Group, db_index=False)

user = ForeignKey(User, db_index=False)

class Meta:

unique_together = (

‘group’,

‘user’,

)

删除冗余的索引将会是插入和查询更快,而且我们的数据库更轻量。

8. 组合索引中列的顺序(Order of columns in composite index)


自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
img

最后

🍅 硬核资料:关注即可领取PPT模板、简历模板、行业经典书籍PDF。
🍅 技术互助:技术群大佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。
🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

可领取PPT模板、简历模板、行业经典书籍PDF。

🍅 技术互助:技术群大佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。
🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值