由來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