Django的多数据库处理(垂直分库和水平分库)
垂直分库指的是根据应用来分数据库,比如博客一个数据库,论坛一个数据库。水平分库是指,根据某些规则,将同一个应用/表的数据分布在不同的库上。比如根据用户ID把用户的博客文章分布在5个数据库上。
垂直分库,可以参考如下的文档。
Easy Multi-Database Support for Django
http://www.eflorenzano.com/blog/post/easy-multi-database-support-django/
这篇文章的主要思路是,继承Manager创建一个MultiDBManager,在建立Model的时候用MultiDBManager替换默认的Manager,在MultiDBManager构造时指定数据库。
核心的设计思路是,在外部设置settings的数据库连接的值,在db内部使用。
这样作其实是很费劲的,因为django在设计之初就没有考虑到multi-db的问题,connection都是直接读取的setting的配置,所有和数据库相关的地方都是“静态”的。
水平分库,可以参考如下的讨论链接,但是这家伙只是给出了思路,不过分析的很全面。
Proposal: user-friendly API for multi-database support
http://groups.google.com/group/django-developers/browse_thread/thread/9f0353fe0682b73
因为我的代码很乱(夹杂其他功能),所以我也只给出思路和部分代码。假设一个应用场景如下:用户的消息,根据用户的ID(数字)分布在2个数据库中,则用户ID对2求余就可以了。改动的地方,主要是BaseDatabaseWrapper(这是数据库连接的wraper,也是多类型(mysql,postgresql之类)数据库支持的基类),为__init__函数增加传入数据库编号参数。其次增加一个Manager,可以传入数据库编号,选择数据库连接。最后是,改动模型,CRUD时增加数据库编号参数。
1.setting.py
2.模拟一个settings.py出来,还有别的办法,我当时用的笨办法。
3.修改django安装文件下的django/db/backends/__init__.py文件,改完要重新安装。如果你直接修改/usr/lib/python2.5/site-packages/django/下的,则不需要。
4.好了,一个可以传入链接参数的connection就做好了。和第一个参考链接里面的差不多,还是有点差异。处理数据库连接,有两个地方一个是QuerySet一个是insert。
5.models的定义,也需要额外的处理,一般情况下的调用如:userMsg.objects.filter()/get()之类的,如果是水平分库,则需要指定连接到哪个数据库。因此,我定义了一个objects(seg)的方法,可以传入数据编号。同时,保存的时候,一般是这样的:usermsg.save(),如果要分库,则改为usermsg.save(seg)就可以了。
6.views中的使用,和django的使用没有太大区别,就上面的两个方法的差异。如果不需要水平切分,就不要使用上面两个方法,仅仅设置_default_manager = MultiManager('msg'),调用save()时,默认会连msg_0;如果调用save(1),就会连第msg_1。
经一步的思考:
1.数据库事务的处理。在多数据库的情况下,针对不同的数据库,是无法使用事务的。因此,在架构上需要考虑异常和补偿性的事务。在同一个数据库上事务的处理,需要改写transication.py,或者获取当前connection,再进行事务的处理。
2.扩容的问题。增加数据库后,会导致数据重新分布,那么就涉及到数据迁移的问题,一个办法是分布规则,考虑到扩容的问题,新的规则兼容老的规则,旧有的数据不会变化。另外一种就是,可以平滑的在库之间移动数据。这两个都是很麻烦的问题。
垂直分库,可以参考如下的文档。
Easy Multi-Database Support for Django
http://www.eflorenzano.com/blog/post/easy-multi-database-support-django/
这篇文章的主要思路是,继承Manager创建一个MultiDBManager,在建立Model的时候用MultiDBManager替换默认的Manager,在MultiDBManager构造时指定数据库。
核心的设计思路是,在外部设置settings的数据库连接的值,在db内部使用。
- for name, database in settings.DATABASES.iteritems():
- for key, value in database.iteritems():
- setattr(settings, key, value)
- do something
这样作其实是很费劲的,因为django在设计之初就没有考虑到multi-db的问题,connection都是直接读取的setting的配置,所有和数据库相关的地方都是“静态”的。
水平分库,可以参考如下的讨论链接,但是这家伙只是给出了思路,不过分析的很全面。
Proposal: user-friendly API for multi-database support
http://groups.google.com/group/django-developers/browse_thread/thread/9f0353fe0682b73
因为我的代码很乱(夹杂其他功能),所以我也只给出思路和部分代码。假设一个应用场景如下:用户的消息,根据用户的ID(数字)分布在2个数据库中,则用户ID对2求余就可以了。改动的地方,主要是BaseDatabaseWrapper(这是数据库连接的wraper,也是多类型(mysql,postgresql之类)数据库支持的基类),为__init__函数增加传入数据库编号参数。其次增加一个Manager,可以传入数据库编号,选择数据库连接。最后是,改动模型,CRUD时增加数据库编号参数。
1.setting.py
- DATABASE_ENGINE = 'mysql'
- DATABASE_NAME = 'master'
- DATABASE_USER = '001'
- DATABASE_PASSWORD = ''
- DATABASE_HOST = '127.0.0.1'
- DATABASE_PORT = '3306'
- DATABASES = dict(
- msg_0 = dict(
- DATABASE_NAME='msg0',
- ),
- msg_1 = dict(
- DATABASE_NAME='msg1',
- ),
- )
2.模拟一个settings.py出来,还有别的办法,我当时用的笨办法。
- class DBSetting:
- def __init__(self,settings,db):
- self.DEBUG = False
- self.DATABASE_HOST = settings.DATABASE_HOST
- self.DATABASE_PORT = settings.DATABASE_PORT
- self.DATABASE_NAME = settings.DATABASE_NAME
- self.DATABASE_USER = settings.DATABASE_USER
- self.DATABASE_PASSWORD = settings.DATABASE_PASSWORD
- if db:
- dbdi = settings.DATABASES[db]
- if dbdi.has_key('DATABASE_HOST'):
- self.DATABASE_HOST = dbdi['DATABASE_HOST']
- if dbdi.has_key('DATABASE_PORT'):
- self.DATABASE_PORT = int(dbdi['DATABASE_PORT'])
- if dbdi.has_key('DATABASE_NAME'):
- self.DATABASE_NAME = dbdi['DATABASE_NAME']
- if dbdi.has_key('DATABASE_USER'):
- self.DATABASE_USER = dbdi['DATABASE_USER']
- if dbdi.has_key('DATABASE_PASSWORD'):
- self.DATABASE_PASSWORD = dbdi['DATABASE_PASSWORD']
- if dbdi.has_key('DATABASE_SEGMENT'):
- self.DATABASE_SEGMENT = dbdi['DATABASE_SEGMENT']
3.修改django安装文件下的django/db/backends/__init__.py文件,改完要重新安装。如果你直接修改/usr/lib/python2.5/site-packages/django/下的,则不需要。
- class BaseDatabaseWrapper(local):
- #增加构造参数,把settings传入
- def __init__(self,settings,**kwargs):
- self.connection = None
- self.queries = []
- self.options = kwargs
- self.settings = settings
- #...此处省略了代码...
- def cursor(self):
- #把传入的settings传递给子类
- cursor = self._cursor(self.settings)
- if self.settings.DEBUG:
- return self.make_debug_cursor(cursor)
- return cursor
4.好了,一个可以传入链接参数的connection就做好了。和第一个参考链接里面的差不多,还是有点差异。处理数据库连接,有两个地方一个是QuerySet一个是insert。
- class MultiManager(models.Manager):
- #group指的是垂直分库的标识,比如blog,bbs之类的
- def __init__(self, group, *args, **kwargs):
- self.group = group
- super(MultiManager, self).__init__(*args, **kwargs)
- #segment指的是用blog之下的哪个库
- #比如settings.py里面的blog_0,blog_1,那么segment就可能是0和1
- def choiceConn(self,segment):
- self.segment = segment
- def _getConn(self):
- if self.group:
- if self.segment:
- key = self.group+'_'+str(self.segment)
- else:
- key = self.group+‘_0’ #默认连第一个
- conn = connPool.getConn(key)
- return conn
- else:
- return None
- def get_query_set(self):
- conn = self._getConn()
- if conn:
- query = sql.Query(self.model,conn)
- queryset = QuerySet(self.model,query)
- else:
- queryset = super(MultiManager, self).get_query_set()
- return queryset
- def _insert(self, values, return_id=False, raw_values=False):
- conn = self._getConn()
- if conn:
- query = sql.InsertQuery(self.model, conn)
- query.insert_values(values, raw_values)
- ret = query.execute_sql(return_id)
- query.connection._commit()
5.models的定义,也需要额外的处理,一般情况下的调用如:userMsg.objects.filter()/get()之类的,如果是水平分库,则需要指定连接到哪个数据库。因此,我定义了一个objects(seg)的方法,可以传入数据编号。同时,保存的时候,一般是这样的:usermsg.save(),如果要分库,则改为usermsg.save(seg)就可以了。
- class userMsg(models.Model):
- msgid = models.AutoField(primary_key = True)
- userid = models.IntegerField()
- message = models.CharField(max_length = 128)
- _default_manager = MultiManager('msg')
- @staticmethod
- def objects(seg):
- log('userMsg.objects(seg)',str(seg))
- userMsg._default_manager.choiceConn(seg)
- return userMsg._default_manager
- def choiceConn(self,seg):
- userMsg._default_manager.choiceConn(seg)
6.views中的使用,和django的使用没有太大区别,就上面的两个方法的差异。如果不需要水平切分,就不要使用上面两个方法,仅仅设置_default_manager = MultiManager('msg'),调用save()时,默认会连msg_0;如果调用save(1),就会连第msg_1。
经一步的思考:
1.数据库事务的处理。在多数据库的情况下,针对不同的数据库,是无法使用事务的。因此,在架构上需要考虑异常和补偿性的事务。在同一个数据库上事务的处理,需要改写transication.py,或者获取当前connection,再进行事务的处理。
2.扩容的问题。增加数据库后,会导致数据重新分布,那么就涉及到数据迁移的问题,一个办法是分布规则,考虑到扩容的问题,新的规则兼容老的规则,旧有的数据不会变化。另外一种就是,可以平滑的在库之间移动数据。这两个都是很麻烦的问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现