django分库分表的优化

Django分表方案

方案一:轮询方式分表

当系统数据越来越多的时候,查询变得缓慢,即使加了索引,由于表数据的增加,索引的维护也会成为数据库性能的限制问题,所以此时可以通过分表,将数据通过某种准则分别存储到不同的表中,以实现缓解单表的压力。

分表的方法大部分都是通过主键id取数据库分表个数的余。最简单的方法就是定义多个Model对象,然后通过一个map的映射,当我们获取具体的Model的时候,直接就通过id与表个数取余,然后通过map的映射我们可以获取到具体的表model对象。

class User(models.Model): user_id = models.IntegerField(primary_key=True)user_name = models.CharField(max_length=256)password = models.CharField(max_length=256)class User0(User):class Meta:db_table = 'user_0'
class User1(User):class Meta:db_table = 'user_1'
class User2(User):class Meta:db_table = 'user_2'
user_model_map = {0: User0,1: User1,2: User2,
}
table_num = 3
# 获取表的model
def get_user_db_model(user_id):key = user_id % table_numreturn user_model_map[key]

但如果表数量躲到几百甚至上千,这种方案就很不使用。

所以我们使用第二种方案,首先我们需要思考为什么在我们使用User.objects.first()的时候,Django可以找到具体的User表,这是因为设置了Meta.db_table具体名称,django可以找到具体的表进行数据的查询。

如果是多张表呢,我们可以想到,可以动态的创建多个表的具体名称,这样的话,在我们程序运行过程中,只要我们传入一个id,此时可以动态的获取到具体的表的名称,就可以找到具体的表,实现具体的分表作用。

要想实现动态数据变更,就需要使用到反射,Python原生支持反射的,我们可以使用type完成新类的创建。实际上主要的功能就是获取表model的时候动态修改内部的Meta.db_table部分的信息即可。

SHARD_TABLE_NUM = 3
class User(models.Model):@classmethoddef get_student_db_model(cls, user_id=None):suffix = user_id % SHARD_TABLE_NUMtable_name = 'user_%s' % suffix# construct a private field _user_model_dict to store the model,# it can help to accelerate the speed of get modelif table_name in cls._user_model_dict:return cls._user_model_dict[table_name]class Meta:db_table = table_nameattrs = {'__module__': cls.__module__,'Meta': Meta}# the first param is obejct name# second: class# the class field infouser_db_model = type(str('User_%s') % suffix, (cls,), attrs)cls._user_model_dict[table_name] = user_db_modelreturn user_db_model_user_model_dict = {}  # help to reuse the model objectid = models.AutoField(primary_key=True)user_name = models.CharField(max_length=256)password = models.CharField(max_length=256)class Meta:abstract = True

上面的代码通过编写一个类方法,先获取到表的名称,具体的获取方法就是采用id取余的方法来实现的。在获取到表名之后,通过class Meta对象,最后通过attrs的属性装配,最后由type实现新类的构建。

def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__"""type(object) -> the object's typetype(name, bases, dict) -> a new type # 构建新的type即类对象# (copied from class doc)"""pass

最后就可以实现具体的分表操作,但是需要注意,当我们插入的时候,因为需要提前知道id,这个时候最好采用一个辅助的Model类来协助id的生成,便于我们后续具体分表的实施,具体表的设计如下:

# the sharding table of Django
class UserIndex(models.Model):id = models.AutoField(primary_key=True)class Meta:db_table = 'user_index_tab'

方案二:动态迁移

通过某一张表的一个字段password_code进行动态的创建models类加入app的model应用中心去

#动态创建模型
def create_task_detail_model(password_code):if password_code:classname = f'TaskDetail_{password_code}'class Meta:db_table = classnamemodel_class = type(classname, (models.Model,), {'task': models.ForeignKey(Task, on_delete=models.CASCADE, related_name='details', verbose_name='任务'),'user_id': models.IntegerField(verbose_name='用户ID'),'created_time': models.DateTimeField(auto_now_add=True, verbose_name='创建时间'),'is_complet': models.BooleanField(default=False, verbose_name='是否完成'),'__str__': lambda self: f"{self.task.password_code} - {self.user_id}",'Meta': Meta,'__module__': __name__,})# Register the model with the app's registryapps.all_models['your_app_name'][classname] = model_classreturn model_classelse:classname = f'TaskDetail'class Meta:db_table = classnamemodel_class = type(classname, (models.Model,), {'task': models.ForeignKey(Task, on_delete=models.CASCADE, related_name='details', verbose_name='任务'),'user_id': models.IntegerField(verbose_name='用户ID'),'created_time': models.DateTimeField(auto_now_add=True, verbose_name='创建时间'),'is_complet': models.BooleanField(default=False, verbose_name='是否完成'),'__str__': lambda self: f"{self.task.password_code} - {self.user_id}",'Meta': Meta,'__module__': __name__,})# Register the model with the app's registryapps.all_models['your_app_name'][classname] = model_classreturn model_class

动态的使用模型类

model_class = apps.get_model('your_app_name', class_name)

开启服务就加入到apps中的model中就使用信号机制

在signalstask.py中配置自动装配逻辑(前提是在对应的表存在的情况下装配的)

@receiver(startup_complete)
def create_dynamic_models(sender, **kwargs):Task = apps.get_model('your_app_name', 'Task')  # Replace with the actual name of your app# Iterate over existing tasks and create corresponding modelsfor task in Task.objects.all():create_task_detail_model(task.password_code)

apps.py中配置

from django.apps import AppConfigclass TaskmanageConfig(AppConfig):default_auto_field = 'django.db.models.BigAutoField'name = 'your_app_name'def ready(self):import your_app_name.signalstask # 导入信号文件以进行连接post_migrate.connect(self.on_startup_complete, sender=self)def on_startup_complete(self, sender, **kwargs):from your_app_name.signalstask import startup_complete# 在这里发送服务启动完成的信号startup_complete.send(sender=self)

Django分库方案

分库的方案有两种,

采取路由的方式设置具体的app_label,按照app_label的不同分配到不同的库
采用自己编写方法,在具体的Model中配置好相应的库从而实现分库

1、DB路由方式

DB路由方式,设置一个DB路由的规则,根据规则的不同,对应的数据库的读写会映射到不同的库上面,以User为例,假设sharding项目的数据存储到sharding库中,sharding_one项目的数据存储到sharding_one数据库中,此时可以编写一个DbRouter,如下

class DbRouter(object):def db_for_read(self, model, **hints):if model._meta.app_label == 'sharding_one': # 判断app_label标签,这个可以在model中设置return 'sharding_one'return Nonedef db_for_write(self, model, **hints):if model._meta.app_label == 'sharding_one':return 'sharding_one'return None

为什么不为sharding_one就返回None呢,因为这里仅有两个项目,一个对应着sharding库,一个对应着sharding_one库,而django程序有个默认库default,返回None的话表示直接对应sharding库。

具体的Model中的设置:

# sharding_one
class Meta:abstract = Trueapp_label = 'sharding_one'

这样最后只需要在不同的项目中执行数据读取或者写入操作,库的切换就通过DB路由切换到了设置的库,从而实现了分库的操作。

2、Config配置方式

采用Config配置方式是一种比较方便的形式,即在Model中配置一个内部类Config,而Config中的属性则对映射具体的写数据和读数据的库操作。

这种方式的话需要写一个获取DB节点的操作,可以使用objects.using(‘db_name’)指定连接具体的数据库。

首先在model中添加具体的Config配置

class Config:db_for_read = 'sharding_one'db_for_write = 'sharding_one'

写一个获取db节点的方法

这个方法先从Config中获取到具体的数据库节点名称,在指定自己使用当前的库

def get_db_obj(objects):db_name = objects.model.Config.db_for_writereturn objects.using(db_name)

最后写入和查询的代码分别如下:

def search_user_service(user_id):# get_student_db_model()就是前面的分表方案中的设计,获取到具体的指定表的model信息,然后从中获取到objects# 然后通过这个表对象,指定具体的库即可,最后从库中查询出具体的数据obj = get_db_obj(User.get_student_db_model(user_id).objects)user = obj.first()return userdef insert_user_service(user_name, password):index = UserIndex() # 这里是因为分表之后,需要插入数据的话,怎么提前获得到主键呢,采用user_index表获取一个自增的主键,或者按照自己设计的方式生成的主键index.save()user_id = index.id# 这里与之前一样,但是由于是插入,所以对象构造成功之后需要取出内部的model进行数据的填充操作model = get_db_obj(User.get_student_db_model(user_id=user_id).objects).model(user_id, user_name, password)try:model.save() # 最后数据保存即可except Exception as e:print "[insert_user] exception error %s" % e

通过上面的方式我们可以实现具体的分库分表操作,这种方式比较有效,对于分库较多的操作,比如写主库,读从库的操作可以通过这个Config配置直接可以实现,而获取db的时候,需要多补写几个方法,就是指定使用db库的方法,这样看起来会更加的直观!

  1. 小结
    之前是看过学习java的分表操作,由于java比较成熟,各种三方工具包的引入,可以很轻松的完成分表以及分库操作,但对于Python而言,Django的ORM是不支持分库分表的,所以需要构造具体的分库分表逻辑,分表的话可以采用动态构造类Model的方式来实现,分库的话可以借助具体的DB路由的配置,编写路由配置类,来实现具体的库名称的获取,从而实现分库操作,同时我们还可以自己通过给Model添加config的属性,从而利用objects.using(db_name)的方式来获取具体的库,实现分库。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/623046.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 虚拟机

运行时数据区域 方法区:方法区是线程共享的,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。 虚拟机栈:虚拟机栈是线程私有的,其生命周期与线程相同即每个线…

CSC8021_computer network_The Transport Layer

Role of the transport layer • The transport layer is responsible for providing a reliable end-to-end connection between two application processes in a network • Abstracting away the physical subnet • Does not involve intermediate nodes • Takes a netwo…

UML-通信图和交互概览图(通信图和顺序图的区别与联系)

UML-通信图和交互概览图(通信图和顺序图的区别与联系) 一、通信图简介1.消息2.链接 二、通信图和[顺序图](https://blog.csdn.net/weixin_65032328/article/details/135587782)的联系与区别三、交互概览图四、顺序图转化为通信图练习 一、通信图简介 通…

2.2 物理层

2.2 物理层 2.2.1 物理层的基本概念 1、物理层主要解决在各种传输媒体上传输比特0和1的问题,进而给数据链路层提供透明传输比特流的服务 2、由于传输媒体的种类太多(例如同轴电缆、光纤、无线电波等),物理连接方式也有很多例如…

vs c++ qt 打包成exe

1 vs2019QT 打包项目 可执行文件exe_哔哩哔哩_bilibili 2 在开始中 找到 qt5142\5.14.2\msvc2017_64 类似于cmd命令行 3 windeployqt.exe 形态环境变量 qt安装包搜索windeployqt.exe D:\qt\5.15.2\winrt_x64_msvc2019\bin 4 \x64\Release\ vs调到Release x64 重新生成 5 运…

libcurl开源库的编译与使用全攻略

libcurl简介 libcurl 是一个广泛使用的、支持多种协议的、开源的客户端URL传输库,提供了许多用于数据传输的API,例如文件传输、FTP、HTTP、HTTPS、SMTP等。libcurl 的主要特点包括 支持多种协议:libcurl 支持多种协议,如 HTTP、F…

Spring集成

目录 概述1 声朋一个简单的集成流1.1 使用XML定义集成流1.2 使用Java配置集成流1.3 使用Spring lntegration 的 DSL 配置 2 Spring integration 功能概览2.1 消息通道2.2 过滤器2.3 转换器2.4 路由器2.5 切分器2.6 服务激活器2.7 网关2.8 通道适配器2.9 端点模块 概述 就像我们…

JDK8-JDK17版本升级

局部变量类型推断 switch表达式 文本块 Records 记录Records是添加到 Java 14 的一项新功能。它允许你创建用于存储数据的类。它类似于 POJO 类,但代码少得多;大多数开发人员使用 Lombok 生成 POJO 类,但是有了记录,你就不需要使…

逸学Docker【java工程师基础】3.1安装Jenkins

1.下载镜像 docker pull jenkins/jenkins:lts 2.运行容器 docker run -d -u root -p 8080:8080 -p 50000:50000 -v /var/jenkins_home:/var/jenkins_home -v /etc/localtime:/etc/localtime --name jenkins jenkins/jenkins:lts 3.要启动名为 jenkins 的 Docker 容器 docker st…

HarmonyOS-LocalStorage:页面级UI状态存储

管理应用拥有的状态概述 上一个章节中介绍的装饰器仅能在页面内,即一个组件树上共享状态变量。如果开发者要实现应用级的,或者多个页面的状态数据共享,就需要用到应用级别的状态管理的概念。ArkTS根据不同特性,提供了多种应用状态…

OpenGauss源码分析-SQL引擎

所讨论文件大多位于src\common\backend\parser文件夹下 总流程 start_xact_command():开始一个事务。pg_parse_query():对查询语句进行词法和语法分析,生成一个或者多个初始的语法分析树。进入foreach (parsetree_item, parsetree_list)循环…

LeetCode 每日一题 Day 37-43

终于考完试了,寒假期间将会每天持续更新! 447. 回旋镖的数量(Day 37) 给定平面上 n 对 互不相同 的点 points ,其中 points[i] [xi, yi] 。回旋镖 是由点 (i, j, k) 表示的元组 ,其中 i 和 j 之间的欧式距离和 i 和 k 之间的欧…

Ai网站收藏,备注

ai网站 有良心的up会留下地址: 1.【codeformer】https://replicate.com/sczhou/codeformer(在线版);https://pan.baidu.com/s/1L9m6YS9w3oOaywjUncaNyw?pwdiw8f(本地版,4个G) 2.【Magisto】&a…

通过开源端点可见性改善网络安全响应

在当今复杂的数字环境中,企业内的许多不同端点(从数据中心的服务器到咖啡店的笔记本电脑)创建了巨大且多样化的攻击面。每个设备都存在网络安全威胁的机会,每个设备都有其独特的特征和复杂性。攻击者使用的多种攻击媒介不仅是一个…

正则表达式中的“回引用(回溯)”——别名引用与序号引用的差异及正则表达式中的“P”关键字

读到一段巧妙的正则表达式,勾起我对正则表达式欠缺知识点的探寻: P y t h o n Python Python正则表达式中的“回引用(回溯)”——分组别名引用与序号引用的差异及正则表达式中的“P”关键字详情。 (笔记模板由python脚本于2024年01月14日 07:49:35创建&a…

pytorch集智4-情绪分类器

1 目标 从中文文本中识别出句子里的情绪。和上一章节单车预测回归问题相比,这个问题是分类问题,不是回归问题 2 神经网络分类器 2.1 如何用神经网络分类 第二章节用torch.nn.Sequantial做的回归预测器,输出神经元只有一个。分类器和其区别…

第九部分 使用函数 (三)

目录 一、文件名操作函数 1、dir 2、notdir 3、suffix 4、basename 5、addsuffix 6、addprefix 7、join 一、文件名操作函数 下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是 一系列的文件名来对待。 1、dir $(dir <names..>…

QT——connect的第五个参数 Qt::ConnectionType (及qt和c++的多线程的区别)

一直对QT的多线程和c的多线程的区别有疑惑&#xff0c;直到看到文档中这一部分内容才豁然开朗 一.ConnectionType参数的类型和区别 首先是官方文档中对于该枚举值的区别介绍&#xff1a; 对于队列&#xff08;queued &#xff09;连接&#xff0c;参数必须是 Qt 元对象系统已知…

强化学习应用(四):基于Q-learning的物流配送路径规划研究(提供Python代码)

一、Q-learning算法简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于马尔可夫决策过程&#xff08;MDP&#xff09;的问题。它通过学习一个值函数来指导智能体在环境中做出决策&#xff0c;以最大化累积奖励。 Q-learning算法的核心思想是使用一个Q值函数来估计每…

边缘计算:挑战与机遇并存

边缘计算&#xff1a;挑战与机遇并存 在数字化时代&#xff0c;数据成为了驱动创新和经济增长的关键要素。然而&#xff0c;随着数据的不断增长&#xff0c;传统的集中式计算模式已经难以满足实时处理和分析的需求。边缘计算作为一种新兴的计算模式&#xff0c;通过将数据处理…