`
marising
  • 浏览: 4055 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类

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内部使用。

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.扩容的问题。增加数据库后,会导致数据重新分布,那么就涉及到数据迁移的问题,一个办法是分布规则,考虑到扩容的问题,新的规则兼容老的规则,旧有的数据不会变化。另外一种就是,可以平滑的在库之间移动数据。这两个都是很麻烦的问题。

分享到:
评论
10 楼 sxin 2009-11-06  
magician 写道
我有一个mysql表,目前接近4000万行记录,使用了partition,目前没有任何问题

以前数据量1000万的时候还没有做分区,也没有感到任何性能问题,就是维护一次时间长点

我的业务有高峰期大并发插入,和全天较长时间大并发查询,这张大表一次都没崩溃过

我实在搞不懂为什么那么多人说mysql的表上了百万就不行了。我使用的是myisam,如果机器内存够大,使用innodb性能在理论上应该更高。

---------------------------------------------------------------

就Python在应用级实现的水平或者垂直分库而言,我只看过SQLAlchemy的实现。给我的感觉就是,不到万不得已,不要用应用级的数据分区,因为性能太低。

在数据库分区如此成熟的情况下(mysql/postgresql目前bug已经非常少了,oracle/db2就不提了),没有道理要在应用级做这个事情

就算一定要做,也有现成的,pylons是指接用SQL Alchemy和SQL Object,这两个都号称支持水平/垂直分区,可以尝试。Dangjo也可以delegate到SQL Alchemy上来,无非是愿意或者不愿意的问题。


刚刚看到对这个问题的讨论,希望还有人可以关注。

你已经使用了partition,这个是MySQL 5.1才新增的功能,我目前还没有对此做过研究。

对于MySQL,数据量的大小并不是唯一导致其性能问题的所在,还有一个更加重要的东西是连接数,MySQL活动连接数在大于100时性能直线下降,特别是对于MyISAM这种表锁引擎。所以对其进行分库,由多个daemon提供服务来保证稳定性。

我维护一个alexa排名400多的网站,在一次断电事故中memcache缓存失效,导致一个单表不过200万,总共大约5000万的分库连接数瞬间到达了1000而无法稳定工作,只有先将最大连接数降为100,几小时后逐渐恢复缓存后才恢复正常。
9 楼 luckytiger 2009-10-10  
恩 目前也在思考 大表 如何处理这些问题 的情况~~~
8 楼 marising 2009-04-10  
magician 写道

我有一个mysql表,目前接近4000万行记录,使用了partition,目前没有任何问题 以前数据量1000万的时候还没有做分区,也没有感到任何性能问题,就是维护一次时间长点 我的业务有高峰期大并发插入,和全天较长时间大并发查询,这张大表一次都没崩溃过 我实在搞不懂为什么那么多人说mysql的表上了百万就不行了。我使用的是myisam,如果机器内存够大,使用innodb性能在理论上应该更高。 --------------------------------------------------------------- 就Python在应用级实现的水平或者垂直分库而言,我只看过SQLAlchemy的实现。给我的感觉就是,不到万不得已,不要用应用级的数据分区,因为性能太低。 在数据库分区如此成熟的情况下(mysql/postgresql目前bug已经非常少了,oracle/db2就不提了),没有道理要在应用级做这个事情 就算一定要做,也有现成的,pylons是指接用SQL Alchemy和SQL Object,这两个都号称支持水平/垂直分区,可以尝试。Dangjo也可以delegate到SQL Alchemy上来,无非是愿意或者不愿意的问题。

Mysql的查询性能,我试了一下,在单表200w,建索引的情况下,性能还不错,大概0.1s内能返回结果,我用的虚拟机。
分库和分区还是有一些区别的,分区仅仅能在一个机器上,分库可以在多台机器,而且添加机器是非常简单的。
7 楼 marising 2009-04-10  
javaeyebird 写道

这只是最简单的情况。许多网站是:用户的email不能重复,昵称(或者用户名)也不能重复,无论哪个作为分库的规则,另外一个都会使唯一性约束变得困难和复杂
另外主外键关联也很麻烦,一个用户的好友分在多个库里,无法利用主外键和单个数据库的一致性约束,同时查询也变得麻烦,join不能用了,因为相关记录不都在一个库里的

怎么讲呢,既然分库,并发和数据量肯定上去了,否则,分库干嘛呢,还不是单库已经搞不定问了嘛。因此,不能强行的要求按照数据库设计的原则去进行。必要时反规则的事情也得做做。不重复的问题也很好解决的,冗余出一个user email和nick检查表。外键约束就不要设计在数据表中了,靠这个约束真的很麻烦。
6 楼 javaeyebird 2009-04-07  
marising 写道

是这样的,如果根据用户ID的规则去分库,则用户表不建议分库,放在主数据库里面。用户注册数一般不会太大,我看资料说Mysql单表小于100w,查询性能尚可。

如果仍需要对用户表进行分库,肯定还要重新设计分库的规则,比如根据用户的名字做MD5,然后匹配到某个库上;用户的主键生成不能使用mysql的自增长类型,而是用MD5或者UUID(这只是一个思路),也可以自增ID+库ID,比如在库1上的ID可能是11,21,31,41,51,个位是库ID,前面是用户ID。也就是说,分库之后,要保证ID是全局唯一的,而不是在库中唯一的。

这只是最简单的情况。许多网站是:用户的email不能重复,昵称(或者用户名)也不能重复,无论哪个作为分库的规则,另外一个都会使唯一性约束变得困难和复杂
另外主外键关联也很麻烦,一个用户的好友分在多个库里,无法利用主外键和单个数据库的一致性约束,同时查询也变得麻烦,join不能用了,因为相关记录不都在一个库里的
5 楼 magician 2009-04-05  
我有一个mysql表,目前接近4000万行记录,使用了partition,目前没有任何问题

以前数据量1000万的时候还没有做分区,也没有感到任何性能问题,就是维护一次时间长点

我的业务有高峰期大并发插入,和全天较长时间大并发查询,这张大表一次都没崩溃过

我实在搞不懂为什么那么多人说mysql的表上了百万就不行了。我使用的是myisam,如果机器内存够大,使用innodb性能在理论上应该更高。

---------------------------------------------------------------

就Python在应用级实现的水平或者垂直分库而言,我只看过SQLAlchemy的实现。给我的感觉就是,不到万不得已,不要用应用级的数据分区,因为性能太低。

在数据库分区如此成熟的情况下(mysql/postgresql目前bug已经非常少了,oracle/db2就不提了),没有道理要在应用级做这个事情

就算一定要做,也有现成的,pylons是指接用SQL Alchemy和SQL Object,这两个都号称支持水平/垂直分区,可以尝试。Dangjo也可以delegate到SQL Alchemy上来,无非是愿意或者不愿意的问题。
4 楼 marising 2009-04-04  
javaeyebird 写道
事务不是问题,可以用分布式事务,只要使用的数据库系统都支持就好

一个大问题是数据约束和关联
比如用户名不能重复。用户分成两个库,字段的唯一性约束只能保证自身库里没有重复
再比如用户的好友,可能是存放在多个库里的,难以使用数据库的主外键引用

通常要在能够访问全部数据库的上层处理这些约束和关联,代码复杂得多,容易出错,还要用更高的事务隔离级别,导致性能下降


是这样的,如果根据用户ID的规则去分库,则用户表不建议分库,放在主数据库里面。用户注册数一般不会太大,我看资料说Mysql单表小于100w,查询性能尚可。

如果仍需要对用户表进行分库,肯定还要重新设计分库的规则,比如根据用户的名字做MD5,然后匹配到某个库上;用户的主键生成不能使用mysql的自增长类型,而是用MD5或者UUID(这只是一个思路),也可以自增ID+库ID,比如在库1上的ID可能是11,21,31,41,51,个位是库ID,前面是用户ID。也就是说,分库之后,要保证ID是全局唯一的,而不是在库中唯一的。

事务的问题,我的一个思路是,补偿性事务,例如,逻辑比较复杂

insert1() //插入数据1,数据库1上的数据
try:
    insert2() //插叙数据2,数据库2上的数据
except Exception e:
    delete1() //失败,删除数据1

当然,如果分布式事务在性能和一致性上能保证,用分布式事务可能是比较好的解决方案。
3 楼 marising 2009-04-04  
剑事 写道
http://www.eflorenzano.com/blog/post/easy-multi-database-support-django/
以前用过 有外键的时候会有问题 manager被换回默认的 不知你这个解决了没

我的习惯是,在程序中控制外键,而不是交给数据库去处理。所以,我没有注意这个问题,不过我感觉有解决办法,看看换回默认的时机在哪里,改之即可。
2 楼 javaeyebird 2009-04-04  
事务不是问题,可以用分布式事务,只要使用的数据库系统都支持就好

一个大问题是数据约束和关联
比如用户名不能重复。用户分成两个库,字段的唯一性约束只能保证自身库里没有重复
再比如用户的好友,可能是存放在多个库里的,难以使用数据库的主外键引用

通常要在能够访问全部数据库的上层处理这些约束和关联,代码复杂得多,容易出错,还要用更高的事务隔离级别,导致性能下降
1 楼 剑事 2009-04-03  
http://www.eflorenzano.com/blog/post/easy-multi-database-support-django/
以前用过 有外键的时候会有问题 manager被换回默认的 不知你这个解决了没

相关推荐

    Python-DjangoHorizon用于Django应用程序的简单数据库分片水平分区库

    它是一个专门为Django应用程序设计的数据库分片(水平分区)工具,旨在帮助开发者实现数据的分布式存储和管理,提高系统的可扩展性和性能。 **1. 数据库分片基础概念** 数据库分片是将一个大型数据库分解为多个较...

    django-horizo​​n:适用于Django应用程序的简单数据库分片(水平分区)库

    1. **概念**:数据库分片是数据库垂直分割(根据列进行划分)和水平分割(根据行进行划分)的一种方式,通常用于大型分布式系统中。水平分区将数据按某种规则分布到多个物理数据库上,每个数据库存储一部分数据,以...

    Python库 | django_sharding-4.0.0-py2-none-any.whl

    数据库分片是数据库垂直分区的一种策略,它将一个大数据库分为多个相互独立的小数据库,每个小数据库都包含一部分数据。这种设计有助于提高读写性能、扩展性,并能改善故障恢复能力。在`django_sharding`中,开发者...

    WEB数据库编程.rar

    SQL(Structured Query Language)是用于管理和处理关系数据库的标准语言。学习SQL包括了解数据查询、插入、更新、删除(DML)、表结构定义(DDL)、索引创建、视图、存储过程和触发器等。熟练掌握SQL语句是进行Web...

    HITSZ 2021 年秋季学期「数据库设计」课程实验项目,基于 MySQL+Django 实现校园食堂点餐系统。.zip

    它支持水平扩展(如通过分片、复制等技术)和垂直扩展(如增加硬件资源),以应对大规模数据存储和高并发访问的需求。 安全性与管理工具 MySQL提供了一系列安全措施,如用户账户管理、访问权限控制、SSL/TLS加密...

    基于python+Django+Mysql的校园二手交易市场.zip

    它支持水平扩展(如通过分片、复制等技术)和垂直扩展(如增加硬件资源),以应对大规模数据存储和高并发访问的需求。 安全性与管理工具 MySQL提供了一系列安全措施,如用户账户管理、访问权限控制、SSL/TLS加密...

    RhymeSearch 疯狂押韵 - 基于Django + Mysql 的中文押韵词语搜索网站.zip

    它支持水平扩展(如通过分片、复制等技术)和垂直扩展(如增加硬件资源),以应对大规模数据存储和高并发访问的需求。 安全性与管理工具 MySQL提供了一系列安全措施,如用户账户管理、访问权限控制、SSL/TLS加密...

    djangocon_2015_talk:DjangoCon 2015 Talk-与Django进行大规模电子商务

    5. **数据库设计**:如何进行数据建模,避免N+1查询问题,以及如何合理分表分库,以提高数据访问效率。 6. **安全实践**:使用Django提供的安全机制,如CSRF保护、XSS防护,以及如何处理支付和用户数据的安全。 7....

    大型网站技术架构:核心原理与案例分析

    - **数据库优化**:包括读写分离、主从复制、分库分表、NoSQL数据库等,以提高数据处理能力。 - **异步处理**:通过消息队列(如RabbitMQ、Kafka)进行任务解耦,实现高并发下的后台处理。 - **CDN内容分发网络**...

    大型网站架构演变和知识体系

    4. **数据库设计**:包括关系型数据库如MySQL、非关系型数据库如MongoDB,以及分库分表、读写分离等策略。 5. **负载均衡与反向代理**:如Nginx、HAProxy等,它们在系统中起到流量调度和静态资源处理的作用。 6. *...

    一个美食网站的源码 包括前后台

    后端则是处理数据和逻辑的部分,如用户登录验证、数据库操作、动态内容生成等。 1. **前端设计与开发** - HTML/CSS/JavaScript:这是构建网站的基本技术栈,HTML定义网页内容,CSS负责样式和布局,JavaScript则...

    Python高并发解决方案实现过程详解

    3. 分库分表:通过水平或垂直拆分,分散数据到多个数据库,减轻单个数据库的压力。 4. 读写分离:将读操作分配给从库,写操作在主库进行,提高系统并发处理能力。 五、使用缓存 Python的Django框架等提供了内置的...

    精典渔具用品购物商城网站源代码

    2. **后端开发**:后端通常采用PHP、Python的Django或Flask、Java的Spring Boot等框架,处理用户请求、数据存储和业务逻辑。比如用户登录验证、商品库存管理、订单状态更新等。 3. **数据库设计**:MySQL、...

Global site tag (gtag.js) - Google Analytics