当前位置: 首页 > article >正文

Django与数据库

我叫补三补四,很高兴见到大家,欢迎一起学习交流和进步

今天来讲一讲alpha策略制定后的测试问题

mysql配置

Django模型体现了面向对象的编程技术,是一种面向对象的编程语言和不兼容类型能相互转化的编程技术,这种技术也叫ORM(Object Relation Mapping,ORM)对象关系映射,Django的ORM功能十分强大,极大提高了开发效率

在Django当中配置数据库的连接非常简单

settings.py文件当中会提前为你创建好一个DATABASE(类型为字典):

# 这是settings.py的内容

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.mysql',  # 配置引擎

        'OPTIONS': {

            'read_default_file': '/path/to/my.cnf',  # 配置文件路径

        },

        'USER': 'yonghu',  # 数据库用户名

        'PASSWORD': 'mima',  # 数据库密码

        'HOST': '127.0.0.1',  # 数据库服务监听IP

        'PORT': '3306',  # 数据库服务监听端口

        'NAME': 'data',  # 数据库名字

    }

}

配置连接有一定顺序

如果配置中定义了OPTIONS,其中定义的连接信息则会优先被使用,如果没有定义OPTIONS,则配置当中的USER、NAME、PASS WORD、HOST、PORT等信息会依次被使用到

DATABASE定义的数据库数量不受限制,但是必须定义一个名为default的数据库,数据库支持如下配置:

ENGINE:配置定义数据库后端:Django自带支持的后端有:PostgreSQL、MYSQL、SQLite、Oracle

HOST:指定连接数据库的主机地址,空字符串则输出默认的地址localhost,也可以使用指定用于连接的套字路径

NAME:数据库名

CONN_MAX_AGE:一个连接的周期,比如设置为5,则会在请求结束5秒后段开连接

OPTIONS:默认为空,用于连接字典的额外参数

PASSWORD:数据库密码,默认为空(用环境变量)

PORT:数据库的端口号

USER:连接数据库的用户名

连接池

在客户端向服务器发出访问请求以后,需要建立一个到数据端的连接,每进行一次访问操作,就会创建一个连接,在mysql当中,这样的连接由max_connection来配置。大部分情况下,这种连接是网络连接,链接过程当中会使用网络套接字,这是一个耗时的操作,大大增加了服务器的压力,为了缓解这种问题,我们可以使用连接池,也就是将数据库连接放到应用程序的缓存当中,在应用程序需要多数据库发出请求时,先从连接池获取连接,再使用这个连接请求数据库

简单来讲,连接池就是通过预先创建一些保持长期为打开状态的接口,从而减少频繁开关连接的开销,Django在首次进行数据库连接后,这个连接会长期在打开状态,只有当超过生命时间周期后才会被关闭,Django本身也并不具备连接池功能,但我们可以通过第三方库来实现,例如django-db-connection-pool 、psycopg2-binary、django_db_polling

实操

不同的库对于连接池的配置有不同的要求,这里用django-db-connection-pool

首先先下载:

pip install django-db-connection-pool

然后在settings.py当中添加相应的配置:

# settings.py

INSTALLED_APPS = [

    ...

    'django_db_connection_pool',

    ...

]



DATABASES = {

    'default': {

        'ENGINE': 'django_db_connection_pool.backends.mysql',  # 使用连接池的数据库引擎

        'NAME': 'your_database_name',

        'USER': 'your_database_user',

        'PASSWORD': 'your_database_password',

        'HOST': 'localhost',

        'PORT': '3306',

        'POOL_OPTIONS': {

            'POOL_SIZE': 10,  # 连接池大小

            'MAX_OVERFLOW': 20,  # 最大溢出连接数

        },

    }

}

Django迁移工具

Django提供的迁移工具可以将对模型所做的更改应用到数据库,从而修改对应的表单和数据库,Django当中提供了三个常用命令,分别是migrate、makemigration、sqlmigrate

其中,migrate命令负责将迁移应用到数据库;makemigration负责将模型变动转换成迁移;sqlmigrate会输出应用变动时实际执行的SQL语句。

具体使用:

1.`makemigrations`命令

`makemigrations`用于生成迁移文件,将模型的变更转换为迁移脚本。

基本用法

python manage.py makemigrations

此命令会检查所有已安装应用的模型定义,与数据库当前状态进行比较,并生成必要的迁移文件。

指定应用

如果只想为特定应用生成迁移文件,可以指定应用名称:

python manage.py makemigrations your_app_name

这会为`myapp`应用生成迁移文件。

查看迁移计划

在生成迁移文件之前,可以使用`--plan`选项查看即将执行的迁移操作:

python manage.py makemigrations --plan


这有助于了解迁移的具体内容。

2.`migrate`命令

`migrate`用于将迁移文件应用到数据库,更新数据库结构以匹配模型。

基本用法

python manage.py migrate


此命令会应用所有未应用的迁移文件。

指定应用

如果只想应用特定应用的迁移,可以指定应用名称:

python manage.py migrate your_app_name

这会只应用`myapp`应用的迁移。

3.`sqlmigrate`命令

`sqlmigrate`用于查看特定迁移文件对应的 SQL 语句,但不会实际执行这些语句。

基本用法

python manage.py sqlmigrate app_name migration_name

例如,查看`myapp`应用中名为`0001_initial`的迁移文件的 SQL 语句:

python manage.py sqlmigrate myapp 0001

这会输出该迁移文件对应的 SQL 语句。

进阶用法

• 指定数据库:


  python manage.py sqlmigrate myapp 0001 --database=secondary_db

这会在多数据库环境中指定数据库。

• 列出迁移文件:

  python manage.py sqlmigrate myapp --list

这会列出所有可用的迁移文件及其状态。

通过以上命令,你可以有效地管理 Django 项目的数据库迁移,确保模型与数据库结构始终保持一致。

数据库创建完成,Django会自动为模型提供一个数据库抽象API,允许创建,检索,更新和删除对象,因此,我们不需要学习mysql语言,仅仅靠Django当中提供的函数就能实现对数据库的增删改查操作,不管是增删还是改查,首先要对数据库进行保存,所以我们要学习Django当中的save()方法

简单案例:

python manage.py shell

from myapp.models import Person

person = Person(name="Alice", age=30)

person.save()

在该案例中在数据库当中新写入了一个person对象,同时,会自动获取一个自增的主键

获取数据表当中的对象:

 all_products =Product.objects.all()

通过过滤器获取符合条件的子集:

 Product.objects.filter(date_created__year=2018)

多个过滤器连接:

Product.objects.filter(date_created__year=2018).exclude(date_created__month=12)

这在sql里面是AND的语句表达,如果想用OR语句查询可以使用Q对象:

Product.objects.filter(

    Q(title__startswith="Women") | (Q(date_created=date(2005, 5, 2)) | Q(date_created=date(2005, 5, 6)))

)

这段代码的目的是从一个名为 Product 的模型中检索一个对象,该对象满足以下条件之一:• 标题以"Women"开头。• 创建日期为2005年5月2日或2005年5月6日。

懒加载和缓存

QuerySet采用懒加载的机制(只有要用到结果时才会访问数据库),因此,访问数据库时,要采用遍历、切片、len()、list()、bool()等方式进行访问,为了降低数据库的负荷,每个QuerySet都会保留一份缓存

>>> print([p.date_created for p in Product.objects.all()])    

>>> print([p.title for p in Product.objects.all()])

因此,两次输入 print([p.date_created for p in Product.objects.all()]) 的结果很可能不同

也可以只是对数据集做一些简单的运算并返回结果(可以用count()查询数量,用Avg查询平均值等等)

数据库事务

将数据库上的许多操作,例如读取数据库、对象、写入、获取锁封装成一个工作单元,整个单元内的操作要么都成功,要么都失败,这就叫事务,在Django当中,提供了控制数据库事务管理的方法,要启用其中将每一个请求包装在事务中的功能,可以在数据库的配置中设置:ATOMIC_REQUESTS为TRUE来开启此功能

此功能的大概工作流程如下:调用视图函数之前,Django会启动一个事务,如果生成的响应没有问题,Django就提交事务,否则就会回滚事务

(也可以使用atomic()上下文管理器):

from django.db import transaction



def my_view(request):

    try:

        with transaction.atomic():

            # 在这里执行数据库操作

            ...

    except Exception as e:

        # 处理异常,事务会自动回滚

        ...

return HttpResponse('Success or Error')



from django.db import transaction



@transaction.atomic  # 作为装饰器使用

def viewfunc(request):

    do_stuff()



def anotherviewfunc(request):

    with transaction.atomic():  # 作为上下文管理器使用

        do_more_stuff()

需要注意:视图函数的执行包含在事务当中,但中间件和模板的渲染是在事务之外运行的

自动提交

 自动提交(Autocommit)模式是指数据库在执行每个单独的 SQL 语句后立即将其提交到数据库。这意味着每个操作都是一个独立的事务,没有事务的开始和结束控制。这种模式通常用于简单的数据库操作,其中不需要复杂的事务控制。在 Django 中,默认情况下,数据库操作是自动提交的。这意味着如果你不显式地使用事务管理,每个数据库操作(如  .save() ,  .delete() ,  .update()  等)都会立即被提交。

可以在配置文件当中设置禁止

AUTOCOMMIT = False

提交后执行操作

在数据库操作中,"提交后执行操作"通常指的是在事务提交之后执行某些特定的操作。在 Django 中,这可以通过几种方式实现:

1.使用信号(Signals)

Django 的信号机制允许你在执行特定操作后发送信号,其他接收者可以监听这些信号并执行相应的操作。例如,你可以在事务提交后发送一个信号。

from django.db import transaction

from django.db.models.signals import post_save

from django.dispatch import receiver



@receiver(post_save, sender=MyModel)

def my_model_saved(sender, instance, **kwargs):

    # 这个函数会在 MyModel 实例保存后执行

    # 确保在事务提交后执行

    if kwargs.get('created', False):

        # 实例创建后执行的操作

        pass



# 在视图中使用事务

def my_view(request):

    try:

        with transaction.atomic():

            my_model_instance = MyModel.objects.create(...)

            # 其他数据库操作...

    except Exception as e:

        # 处理异常

        pass

2.使用`transaction.on_commit`回调

Django 3.6+引入了`transaction.on_commit`回调,允许你在事务提交时执行某些操作。

from django.db import transaction



def my_view(request):

    def callback():

        # 事务提交后执行的操作

        pass



    try:

        with transaction.atomic():

            my_model_instance = MyModel.objects.create(...)

            # 其他数据库操作...

            transaction.on_commit(callback)

    except Exception as e:

        # 处理异常

        pass

在这个例子中,`callback`函数将在事务成功提交后执行。

3.使用数据库触发器(Triggers)

对于更复杂的场景,你可以在数据库层面使用触发器(Triggers)。触发器是数据库对象,它们在特定事件发生时自动执行。例如,你可以创建一个触发器,在插入或更新记录后执行某些操作。

CREATE TRIGGER after_insert_my_model

AFTER INSERT ON my_model

FOR EACH ROW

BEGIN

    -- 执行操作

END;

请注意,使用触发器需要对数据库有更深入的了解,并且不同数据库的触发器语法可能不同。

注意事项

• 确保在事务提交后执行的操作不会违反事务的隔离性或一致性。

• 使用信号或回调时,确保它们不会引入死锁或循环依赖。

• 在使用触发器时,确保它们不会与应用逻辑冲突。

通过这些方法,你可以在 Django 中实现在事务提交后执行特定操作的需求。选择哪种方法取决于你的具体场景和偏好。

数据库并发控制

多用户同时访问和更改数据库可能会引发冲突,因此,要协调同步事务,在两个活动尝试修改同一个数据库的对象时,有三种情况,两个活动会相互干扰:

  1. 脏读:事务A读取到了事务B的修改后尚未提交的数据,但事务B如果最后没有选择提交,那这种读取方式就叫脏读

  1. 不可重复读:多次读取同一个数据,两次读取结果不一致,这种情况一般发生在其他事务对该数据进行修改并提交

  1. 幻读:A查询后的下一刻数据库被更改,导致查询结果不准确

越是允许缓存当中的过时数据存在,则并发的线程/问题就越多,可能出现的问题也就越大

这会导致什么问题?具体举个例子,A访问数据库时余额为10,然后B也访问了数据库,A从中取出3,也就是原数据库改为7,但B如果进行存5的操作,此时由于数据库并发的问题,B修改数据库为10+5=15。

为了杜绝此类问题出现,需要确保正在处理的资源在工作时不会发生改变

悲观锁

是指实体在应用中存储的整个周期,数据库都处于锁定状态,以此来限制其他用户使用这个数据库当中的实体

可以是修改数据库的人不希望其他人再次过程当中读取实体,也可以是读取数据库的人不希望其他人在此过程当中修改实体

锁的范围可能是整个数据库、表、多行或单行。这些锁分别称为数据库锁、表锁、页锁和行锁。

优点是简单易用,缺点是当用户数量过多时该方法限制了可同时操作的用户数量

在Django中实现悲观锁可以通过`select_for_update()`方法来完成,它利用数据库的行级锁机制,确保在事务提交或回滚之前,其他事务无法修改被锁定的行。

实现步骤

1. 使用`transaction.atomic()`装饰器:确保操作在一个事务中完成,如果操作失败,事务会自动回滚。

2. 调用`select_for_update()`方法:对查询结果加锁,锁定特定的行。

3. 执行业务逻辑:在锁定期间对数据进行修改。

4. 提交或回滚事务:如果操作成功,事务提交;如果失败,事务回滚。

示例代码

以下是一个使用悲观锁的Django视图函数示例,模拟秒杀场景:

from django.db import transaction

from django.http import HttpResponse

from .models import Book, Order

import time

import random



@transaction.atomic

def seckill(request):

    sid = transaction.savepoint()  # 设置保存点

    book = Book.objects.select_for_update().filter(pk=1).first()  # 加行锁

    if book.count > 0:

        print('库存可以,下单')

        Order.objects.create(order_id=str(time.time()), order_name='测试订单')

        time.sleep(random.randint(1, 4))  # 模拟延迟

        book.count -= 1

        book.save()

        transaction.savepoint_commit(sid)  # 提交事务

        return HttpResponse('秒杀成功')

    else:

        transaction.savepoint_rollback(sid)  # 回滚事务

        return HttpResponse('库存不足,秒杀失败')

注意事项

1. 锁的粒度:`select_for_update()`默认加行级锁,但如果查询条件未指定主键或唯一索引,可能会升级为表锁。

2. 死锁风险:在高并发场景下,悲观锁可能导致死锁。需要合理设计事务逻辑,避免长时间持有锁。

3. 数据库支持:`select_for_update()`需要数据库支持事务和行级锁,如MySQL的InnoDB引擎。

通过这种方式,Django可以利用数据库的锁机制实现悲观锁,确保在并发场景下数据的一致性和完整性。

乐观锁

在冲突发生不频繁时,可以考不阻止并发控制,而是选择检测冲突,并且在冲突发生时去解决他

updated = Account.objects.filter(

    id=self.id,

    version=self.version,

).update(

    balance=F('balance') + amount,

    version=F('version') + 1,

)

在原本定义的模型上多加上一个version,update语句其实暗含了检查版本的功能(因为会先检查version字段与传入的version字段是否匹配,只有匹配才会执行更改)

解决冲突的基本途径有:放弃、展示问题让客户决定、合并改动、记录冲突让后来的人决定、无视冲突直接覆盖,在不同的情况下,我们可以选择不同的锁和不同的策略

数据库扩展

纵向扩展

简单来说,数据库的扩展就是让数据库能够处理更多流量和更多读写查询,主流的扩展方法有纵向扩展和横向扩展,纵向扩展采用的是增强单个数据库能力的方法(增强cpu、内存、存储空间等等)

优点:应用层不需要适配;缺点:硬件扩容有上限,成本高

横向扩展

横向扩展采用的是将多个数据库组合起来的方法,和纵向扩展相比,此发可用性高,易于升级且成本低廉,缺点是对技术要求高并且要求对应用层做出更改

几种横向扩展的方法

读写分离

读写分离主要用到了MYSQL的复制功能

允许将一个来自MYSQL数据库服务器的数据自动复制到一个或多个MYSQL数据库服务器,该功能需要在修改Django配置的同时对于MYSQL也做出小的修改,效果是当一台数据库服务宕机后,能通过调整另外一台库以最快的速度恢复服务,由于主从复制是单向的,因此只有主数据库用于写操作,而读操作则可以在多个数据库上进行,因此,我们至少需要两个数据库,一个用于读操作,另一个用于写操作,要使复制功能正常工作,首先要把复制事件写入日志,一般称这个日志为Binlog,每当从数据库连接到主数据库,主数据库就会创建新的线程,然后执行服务器对他的请求,大多请求会是将Binlog传给从服务器并且通知从服务器有新的Binlog写入

从服务器会起两个线程来处理复制,一个称I/O线程,负责连接到主服务器从中读取二进制日志事件(中继日志),另一个称SQL线程,负责在本地的中继日志当中读取事件,然后本地执行

在Django当中提供了多数据库请求路由来实现读写分离

使用步骤如下:

  1. 修改settings.py当中的DATABASES列表
DATABASES = {

    'default': {  # 默认数据库,主数据库

        'NAME': 'multidb',

        'ENGINE': 'django.db.backends.mysql',

        'USER': 'some_user',  # 数据库账号

        'PASSWORD': 'some_password',  # 数据库密码

        'HOST': 'master_host_ip',  # 数据库IP

    },

    'slave': {  # 从数据库

        'NAME': 'multidb',

        'ENGINE': 'django.db.backends.mysql',

        'USER': 'some_user',  # 数据库账号

        'PASSWORD': 'some_password',  # 数据库密码

        'HOST': 'slave_host_ip'  # 数据库IP

    }

}
  1. 将写操作应用到主数据库,读操作应用到从数据库
import random

class RandomRouter:

    def db_for_read(self, model, **hints):

        return random.choice(['replica1', 'replica2'])  # 随机选一个从数据库
  1. 在settings.py当中加上
DATABASE_ROUTERS = ['path.to.DefaultRouter']

垂直分库

比较常见的一种做法是将不同的模块放在不同的数据库中。

通过路由,将不同模块的请求转到不同的数据库当中,首先在settings.py当中配置多个数据库:

DATABASES = {

    'default': {},

    'auth_db': {

        'NAME': 'auth_db',

        'ENGINE': 'django.db.backends.mysql',

        # 其他配置...

    },

    'product_db': {

        'NAME': 'product_db',

        'ENGINE': 'django.db.backends.mysql',

        # 其他配置...

    },

    'order_db': {

        'NAME': 'order_db',

        'ENGINE': 'django.db.backends.mysql',

        # 其他配置...

    },

}

然后根据模型的不同路由请求到不同的数据库

class MultiDatabaseRouter:

    def db_for_read(self, model, **hints):

        if model == User:  # 请求用户数据库

            return 'auth_db'

        elif model == Product:  # 请求商品数据库

            return 'product_db'

        elif model == Order:  # 请求订单数据库

            return 'order_db'

        else:

            return 'default'

在settings.py当中加上

DATABASE_ROUTERS = ['path.to.MultiDatabaseRouter']

水平扩展

当用户数量达到一定规模,单个数据库或数据表无法容纳用户的全部数据,常见的做法就是对数据库中的数据进行水平分区,每个单独的分区称为分片

每个分片承载单独的数据库,以分散负载

分片操作虽然提高了查询性能,单本身也使应用层变得极其复杂,采用该策略需要慎重考虑

常见的分片方式有算法分片和动态分片——算法分片是让数据通过算法写入不同分片,动态分片则需要客户端读取其他存储,确认分片信息,最简单的算法分片是取模算法,因为笔者做不到每天300万的用户访问量所以这部分咱就不讨论了...


http://www.kler.cn/a/570470.html

相关文章:

  • Docker 模拟 kubernetes 的 pod
  • STM32引脚类型
  • Linux IO编程核心要点解析:文件、流与缓冲区实战指南
  • Python配置文件的全面解析与使用
  • WEB10(CTFshow)
  • 算法题笔记(自用)——Python
  • 编程题 - 汽水瓶【JavaScript/Node.js解法】
  • 【含文档+PPT+源码】基于SpringBoot和Vue的编程学习系统
  • CentOS 7 IP 地址设置保姆级教程
  • 动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南
  • Spark核心之02:RDD、算子分类、常用算子
  • cursor for linux
  • MySQL 索引深度解析手册
  • 【二分查找】_240. 搜索二维矩阵 II
  • java常见面试01
  • 比特币暴跌背后
  • 【Elasticsearch】使用ILM自动化滚动操作
  • 基于log4j的自定义traceid实现
  • 推荐1款OCR的扫描仪软件,无需安装,打开即用!
  • µC/OS-III-事件标志