由来python
知乎上的一个问题:Django 分表 怎么实现?mysql
这个问题戳到了Django ORM的痛点,对于多数据库/分库的问题,Django提供了很好的支持,经过using和db router能够很好的完成多数据库的操做。可是说到分表的问题,就有点不那么友好了。但也不是那么难处理,只是处理起来不太优雅。sql
解析
在Django中,数据库访问的逻辑基本上是在Queryset中完成的,一个查询请求,好比:User.objects.filter(group_id=10)
。数据库
其中的objects
其实就是models.Manager
,而Manager
又是对QuerySet的一个包装。而QuerySet又是最终要转换为sql的一个中间层(就是ORM种,把Model操做转换为SQL语句的部分)。因此当咱们写下User.objects
的时候,就已经肯定了要访问的是哪一个表了,这是由class Meta中的db_table决定的。django
class User(models.Model): username = models.CharField(max_length=255) class Meta: db_table = 'user'
理论上讲,咱们能够经过在运行时修改db_table来完成分表CRUD的逻辑,可是the5fire在看了又看源码以后,仍是没找到如何下手。仍是上面的问题,当执行到User.objects
的时候,表已经肯定了,当执行到User.objects.filter(group=10)
的时候只不过是在已经生成好的sql语句中增长了一个where部分语句。因此并无办法在执行filter的时候来动态设置db_table。json
对于问题中说的get也是同样,由于get自己就是在执行完filter以后从_result_cache列表中获取的数据(_result_cache[0])。app
方案一
根据the5fire上面的分析,要想在执行具体查询时修改db_table已是不可能了(固然,若是你打算去重写Model中Meta部分的逻辑以及Queryset部分的逻辑,就当我没说,我只能表示佩服)。框架
因此只能从定义层面下手了。也就是我须要定义多个Model,一样的字段,不一样的db_table。大概是这样。函数
class User(models.Model): username = models.CharField(max_length=255) class Meta: abstract = True class User1(User): class Meta: db_table = 'user_1' # 默认状况下不设置db_table属性时,Django会使用``<app>_<model_name>``.lower()来做为表名 class User2(User): class Meta: db_table = 'user_2'
这样在User.objects.get(id=3)
的时候,若是按照模2计算,那就是User01.objects.get(id=3)
,笨点的方法就是写一个dict:
user_sharding_map = { 1: User1, 2: User2 } def get_sharding_model(id): key = id % 2 + 1 return user_sharding_map[key] ShardingModel = get_sharding_model(3) ShardingModel.objects.get(id=3)
若是真的这么写那Python做为动态语言,还有啥用,你分128张表试试。咱们应该动态建立出User01,User02,....UserN这样的表。
class User(models.Model): @classmethod def get_sharding_model(cls, id=None): piece = id % 2 + 1 class Meta: db_table = 'user_%s' % piece attrs = { '__module__': cls.__module__, 'Meta': Meta, } return type(str('User%s' % piece), (cls, ), attrs) username = models.CharField(max_length=255, verbose_name="the5fire blog username") class Meta: abstract = True ShardingUser = User.get_sharding_model(id=3) user = ShardingUser.objects.get(id=3)
嗯,这样看起来彷佛好了一下,可是还有问题,id=3须要传两次,若是两次不一致,那就麻烦了。Model层要为上层提供统一的入口才行。
class MyUser(models.Model): # 增长方法 BY the5fire @classmethod def sharding_get(cls, id=None, **kwargs): assert id, 'id is required!' Model = cls.get_sharding_model(id=id) return Model.objects.get(id=id, **kwargs)
对上层来书,只须要执行MyUser.sharding_get(id=10)便可。不过这改变了以前的调用习惯 objects.get
。
无论怎么说吧,这也是个方案,更完美的方法就不继续探究了,在Django的ORM中钻来钻去寻找能够hook的点实在憋屈。
咱们来看方案二吧
方案二
ORM的过程是这样的,Model——> SQL ——> Model,在方案一中咱们一直在处理Model——> SQL的部分。其实咱们能够抛开这一步,直接使用raw sql。
QuerySet提供了raw这样的接口,用来让你忽略第一层转换,可是有可使用从SQL到Model的转换。只针对SELECT的案例:
class MyUser(models.Model): id = models.IntegerField(primary_key=True, verbose_name='ID') username = models.CharField(max_length=255) @classmethod def get_sharding_table(cls, id=None): piece = id % 2 + 1 return cls._meta.db_table + str(piece) @classmethod def sharding_get(cls, id=None, **kwargs): assert isinstance(id, int), 'id must be integer!' table = cls.get_sharding_table(id) sql = "SELECT * FROM %s" % table kwargs['id'] = id condition = ' AND '.join([k + '=%s' for k in kwargs]) params = [str(v) for v in kwargs.values()] where = " WHERE " + condition try: return cls.objects.raw(sql + where, params=params)[0] # the5fire:这里应该模仿Queryset中get的处理方式 except IndexError: # the5fire:其实应该抛Django的那个DoesNotExist异常 return None class Meta: db_table = 'user_'
大概这么个意思吧,代码能够再严谨些。
总结
单纯看方案一的话,可能会以为这么大量数据的项目,就别用Django了。其实the5fire第一次尝试找一个优雅的方式hack db_table时,也是一头灰。可是,全部的项目都是由小到大的,随着数据/业务的变大,技术人员应该也会更加了解Django,等到必定阶段以后,可能发现,用其余更灵活的框架,跟直接定制Django成本差很少。
2.type动态建立类:https://blog.csdn.net/wangbowj123/article/details/77162828
type还有一种彻底不一样的功能,动态的建立类。
type能够接受一个类的描述做为参数,而后返回一个类。(要知道,根据传入参数的不一样,同一个函数拥有两种彻底不一样的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)
type能够像这样工做:
type(类名,由父类名称组成的元组(针对继承的状况,能够为空),包含属性的字典(名称和值))
代码以下:
#运用type建立类、添加属性 Test = type("Test",(),{'age':13,'name':"wangbo"}) test = Test() print(test.age) #利用type添加方法 @classmethod #类方法 def testClass(cls): print(cls.name) @staticmethod #静态方法 def testStatic(): print("static method.....") def echo_age(self): print(self.age) Test2 = type("Test2",(Test,),{'echo_age':echo_age,'testStatic':testStatic,'testClass':testClass}) test2 = Test2() test2.echo_age() test2.testStatic() test2.testClass()
元类就是用来建立类的“东西”。你建立类就是为了建立类的实例对象,不是吗?可是咱们已经学习到了Python中的类也是对象。
元类就是用来建立这些类(对象)的,元类就是类的类,你能够这样理解为:
MyClass = MetaClass() #使用元类建立出一个对象,这个对象称为“类”
MyObject = MyClass() #使用“类”来建立出实例对象
你已经看到了type可让你像这样作:
MyClass = type(‘MyClass’, (), {})
函数type其实是一个元类。type就是Python在背后用来建立全部类的元类。如今你想知道那为何type会所有采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来建立字符串对象的类,而int是用来建立整数对象的类。type就是建立类对象的类。你能够经过检查class属性来看到这一点。Python中全部的东西,注意,我是指全部的东西——都是对象。这包括整数、字符串、函数以及类。它们所有都是对象,并且它们都是从一个类建立而来,这个类就是type。
>>> age = 35 >>> age.__class__ <type 'int'> >>> name = 'bob' >>> name.__class__ <type 'str'> >>> def foo(): pass >>>foo.__class__ <type 'function'> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'>
如今,对于任何一个class的class属性又是什么呢?
>>> a.__class__.__class__ <type 'type'> >>> age.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
所以,元类就是建立类这种对象的东西。type就是Python的内建元类,固然了,你也能够建立本身的元类。
3.queryset对象的合并 http://www.javashuo.com/article/p-atkdpqpj-bs.html http://www.yihaomen.com/article/python/533.htm
在用python或者django写一些小工具应用的时候,有可能会遇到合并多个list到一个 list 的状况。单纯从技术角度来讲,处理起来没什么难度,能想到的办法不少,但我以为有一个很简单并且效率比较高的方法是我之前没注意到的。那就是利用 chain 方法来合并多个list. 一样也能够用来合并django 的 QuerySet.
1. python用chain 来合并多个list
chain 是用C实现的,天然性能上比较可靠。下面看下基本用法:
#coding:utf-8 from itertools import chain a = [1,2,"aaa",{"name":"roy","age":100}] b = [3,4] c = [5,6] #items = a + b + c items = chain(a,b,c) for item in items: print item
输出结果以下:
1 2 aaa {'age': 100, 'name': 'roy'} 3 4 5 6
因而可知能够很好的合并成功。
2. 在Django 总用 chain 合并多个QuerySet.
自己若是在Django中若是要合并同一个model的多个QuerySet 的话,是能够采用这种方式的.
#coding:utf-8 from itertools import chain from yihaomen.common.models import Article articles1 = Article.objects.order_by("autoid").filter(autoid__lt = 16).values('autoid','title') articles2 = Article.objects.filter(autoid = 30).values('autoid','title') articles = articles1 | articles2 # 注意这里采用的方式。若是 Model相同,并且没有用切片,而且字段同样时能够这样用 print articles1 print articles2 print articles
这样能很好的工做,但有些局限性,对于Django 来讲不少状况下也够用了,合并到一个 QuerySet 中,而后返回到模板引擎中去处理。
固然也能够用chain 来实现,用chain 来实现会更方便,也没那么多限制条件,即便是不一样的MODEL中查询出来的数据,均可以很方便的合并到一个 list 中去.
#coding:utf-8 from itertools import chain from yihaomen.common.models import Article, UserID articles1 = Article.objects.order_by("autoid").filter(autoid__lt = 16).values('autoid','title') users = UserID.objects.all() items = chain(articles1, users) for item in items: print item
这样作更方便,也很实用, 对于处理某些须要合并的list 而后再传输到某一个地方去的状况下,这样作很方便。
4.django多数据库的使用:https://code.ziqiangxuetang.com/django/django-multi-database.html
本文讲述在一个 django project 中使用多个数据库的方法, 多个数据库的联用 以及多数据库时数据导入导出的方法。
直接给出一种简单的方法吧,想了解更多的到官方教程,点击此处
代码文件下载:project_name.zip(2017年05月01日更新)
1. 每一个app均可以单独设置一个数据库
settings.py中有数据库的相关设置,有一个默认的数据库 default,咱们能够再加一些其它的,好比:
# Database # https://docs.djangoproject.com/en/1.8/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, 'db1': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbname1', 'USER': 'your_db_user_name', 'PASSWORD': 'yourpassword', "HOST": "localhost", }, 'db2': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbname2', 'USER': 'your_db_user_name', 'PASSWORD': 'yourpassword', "HOST": "localhost", }, } # use multi-database in django # add by WeizhongTu DATABASE_ROUTERS = ['project_name.database_router.DatabaseAppsRouter'] DATABASE_APPS_MAPPING = { # example: #'app_name':'database_name', 'app1': 'db1', 'app2': 'db2', }
在project_name文件夹中存放 database_router.py 文件,内容以下:
# -*- coding: utf-8 -*- from django.conf import settings DATABASE_MAPPING = settings.DATABASE_APPS_MAPPING class DatabaseAppsRouter(object): """ A router to control all database operations on models for different databases. In case an app is not set in settings.DATABASE_APPS_MAPPING, the router will fallback to the `default` database. Settings example: DATABASE_APPS_MAPPING = {'app1': 'db1', 'app2': 'db2'} """ def db_for_read(self, model, **hints): """"Point all read operations to the specific database.""" if model._meta.app_label in DATABASE_MAPPING: return DATABASE_MAPPING[model._meta.app_label] return None def db_for_write(self, model, **hints): """Point all write operations to the specific database.""" if model._meta.app_label in DATABASE_MAPPING: return DATABASE_MAPPING[model._meta.app_label] return None def allow_relation(self, obj1, obj2, **hints): """Allow any relation between apps that use the same database.""" db_obj1 = DATABASE_MAPPING.get(obj1._meta.app_label) db_obj2 = DATABASE_MAPPING.get(obj2._meta.app_label) if db_obj1 and db_obj2: if db_obj1 == db_obj2: return True else: return False return None # for Django 1.4 - Django 1.6 def allow_syncdb(self, db, model): """Make sure that apps only appear in the related database.""" if db in DATABASE_MAPPING.values(): return DATABASE_MAPPING.get(model._meta.app_label) == db elif model._meta.app_label in DATABASE_MAPPING: return False return None # Django 1.7 - Django 1.11 def allow_migrate(self, db, app_label, model_name=None, **hints): print db, app_label, model_name, hints if db in DATABASE_MAPPING.values(): return DATABASE_MAPPING.get(app_label) == db elif app_label in DATABASE_MAPPING: return False return None
这样就实现了指定的 app 使用指定的数据库了,固然你也能够多个sqlite3一块儿使用,至关于能够给每一个app均可以单独设置一个数据库!若是不设置或者没有设置的app就会自动使用默认的数据库。
2.使用指定的数据库来执行操做
在查询的语句后面用 using(dbname) 来指定要操做的数据库便可
# 查询 YourModel.objects.using('db1').all() 或者 YourModel.objects.using('db2').all() # 保存 或 删除 user_obj.save(using='new_users') user_obj.delete(using='legacy_users')
3.多个数据库联用时数据导入导出
使用的时候和一个数据库的区别是:
若是不是defalut(默认数据库)要在命令后边加 --database=数据库对应的settings.py中的名称 如: --database=db1 或 --database=db2
数据库同步(建立表)
# Django 1.6及如下版本 python manage.py syncdb #同步默认的数据库,和原来的没有区别 # 同步数据库 db1 (注意:不是数据库名是db1,是settings.py中的那个db1,不过你可使这两个名称相同,容易使用) python manage.py syncdb --database=db1 # Django 1.7 及以上版本 python manage.py migrate --database=db1
数据导出
python manage.py dumpdata app1 --database=db1 > app1_fixture.json python manage.py dumpdata app2 --database=db2 > app2_fixture.json python manage.py dumpdata auth > auth_fixture.json
数据库导入
python manage.py loaddata app1_fixture.json --database=db1 python manage.py loaddata app2_fixture.json --database=db2