(转)Django配置数据库读写分离
转:https://blog.csdn.net/Ayhan_huang/article/details/78784486
转:http://www.cnblogs.com/dreamer-fish/p/5469141.html
目录
简述
对网站的数据库作读写分离(Read/Write Splitting)可以提高性能,在Django中对此提供了支持,下面我们来简单看一下。注意,还需要运维人员作数据库的读写分离和数据同步。
配置数据库
我们知道在Django项目的settings中,可以配置数据库,除了默认的数据库,我在下面又加了一个db2。因为是演示,我这里用的是默认的SQLite,如果希望用MySQL,看这里 。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, 'db2': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db2.sqlite3'),
创建models并执行数据库迁移
这里我简单创建一张产品表
from django.db import models class Products(models.Model): """产品表""" prod_name = models.CharField(max_length=30) prod_price = models.DecimalField(max_digits=6, decimal_places=2)
创建完成后,执行数据库迁移操作:
python manage.py makemigrations # 在migrations文件夹下生成记录,迁移前检查 python manage.py migrate # 创建表 # python manage.py migrate --database default python manage.py migrate --database db2 # 根据settings里的名称逐个迁移
在migrations文件夹下生成记录,并在迁移前检查是否有问题,默认值检查defualt数据库,但是可以在后面的数据库路由类(Router)中通过allow_migrate()方法来指定是否检查其它的数据库。
其实第二步迁移默认有参数python manage.py migrate --database default ,在默认数据库上创建表。因此完成以上迁移后,执行python manage.py --database db2,再迁移一次,就可以在db2上创建相同的表。这样在项目根目录下,就有了两个表结构一样的数据库,分别是db.sqlite3和db2.sqlite3。
读写分离
手动读写分离
在使用数据库时,通过.using(db_name)来手动指定要使用的数据库
from django.shortcuts import HttpResponse from . import models def write(request): models.Products.objects.using('default').create(prod_name='熊猫公仔', prod_price=12.99) return HttpResponse('写入成功') def read(request): obj = models.Products.objects.filter(id=1).using('db2').first() return HttpResponse(obj.prod_name)
自动读写分离
通过配置数据库路由,来自动实现,这样就不需要每次读写都手动指定数据库了。数据库路由中提供了四个方法。这里这里主要用其中的两个:def db_for_read()决定读操作的数据库,def db_for_write()决定写操作的数据库。
定义Router类
新建myrouter.py脚本,定义Router类:
class Router: def db_for_read(self, model, **hints): return 'db2' def db_for_write(self, model, **hints): return 'default'
配置Router
settings.py中指定DATABASE_ROUTERS
DATABASE_ROUTERS = ['myrouter.Router',]
可以指定多个数据库路由,比如对于读操作,Django将会循环所有路由中的db_for_read()方法,直到其中一个有返回值,然后使用这个数据库进行当前操作。
一主多从方案
网站的读的性能通常更重要,因此,可以多配置几个数据库,并在读取时,随机选取,比如:
class Router: def db_for_read(self, model, **hints): """ 读取时随机选择一个数据库 """ import random return random.choice(['db2', 'db3', 'db4']) def db_for_write(self, model, **hints): """ 写入时选择主库 """ return 'default'
分库分表
在大型web项目中,常常会创建多个app来处理不同的业务,如果希望实现app之间的数据库分离,比如app01走数据库db1,app02走数据库
class Router: def db_for_read(self, model, **hints): if model._meta.app_label == 'app01': return 'db1' if model._meta.app_label == 'app02': return 'db2' def db_for_write(self, model, **hints): if model._meta.app_label == 'app01': return 'db1' if model._meta.app_label == 'app02': return 'db2'
例子
别人的例子
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'testly', 'USER': 'root', 'PASSWORD': '123456789', 'HOST':'192.168.1.1', 'PORT':'3306', }, 'hvdb':{ #配置第二个数据库节点名称 'ENGINE': 'django.db.backends.mysql', 'NAME': 'testdjango', #第二个数据库的名称 'USER': 'root', 'PASSWORD': '123456789', 'HOST':'192.168.1.1', 'PORT':'3306', } } DATABASE_ROUTERS = ['tt.db_router.app02Router'] #tt为当前项目名称,db_router为上一步编写的db_router.py文件,app02Router为Router #DATABASE_ROUTERS = ['tt.db_router.app02Router','tt.db_router.app01Router'] #如果定义了多个Router,在此就需要分别指定。注意:这个是有顺序的(先匹配上的规则,就先生效)
# -*- coding: UTF-8 -*- class app02Router(object): #配置app02的路由,去连接hvdb数据库 """ A router to control all database operations on models in the app02 application. """ def db_for_read(self, model, **hints): """ Attempts to read app02 models go to hvdb DB. """ if model._meta.app_label == 'app02': #app name(如果该app不存在,则无法同步成功) return 'hvdb' #hvdb为settings中配置的database节点名称,并非db name。dbname为testdjango return None def db_for_write(self, model, **hints): """ Attempts to write app02 models go to hvdb DB. """ if model._meta.app_label == 'app02': return 'hvdb' return None def allow_relation(self, obj1, obj2, **hints): """ Allow relations if a model in the app02 app is involved. 当 obj1 和 obj2 之间允许有关系时返回 True ,不允许时返回 False ,或者没有 意见时返回 None 。 """ if obj1._meta.app_label == 'app02' or \ obj2._meta.app_label == 'app02': return True return None def allow_migrate(self, db, model): """ Make sure the app02 app only appears in the hvdb database. """ if db == 'hvdb': return model._meta.app_label == 'app02' elif model._meta.app_label == 'app02': return False def allow_syncdb(self, db, model): #决定 model 是否可以和 db 为别名的数据库同步 if db == 'hvdb' or model._meta.app_label == "app02": return False # we're not using syncdb on our hvdb database else: # but all other models/databases are fine return True return None # class app01Router(object): # """ # A router to control all database operations on models in the # aew application. # """ # def db_for_read(self, model, **hints): # """ # Attempts to read aew models go to aew DB. # """ # if model._meta.app_label == 'app01': # return 'default' # return None # def db_for_write(self, model, **hints): # """ # Attempts to write aew models go to aew DB. # """ # if model._meta.app_label == 'app01': # return 'default' # return None # def allow_relation(self, obj1, obj2, **hints): # """ # Allow relations if a model in the aew app is involved. # """ # if obj1._meta.app_label == 'app01' or obj2._meta.app_label == 'app01': # return True # return None # def allow_migrate(self, db, model): # """ # Make sure the aew app only appears in the aew database. # """ # if db == 'default': # return model._meta.app_label == 'app01' # elif model._meta.app_label == 'app01': # return False # return None
class tb05(models.Model): #该model使用default数据库 name=models.CharField(max_length=100,primary_key=True,unique=True) ip=models.GenericIPAddressField() rating = models.IntegerField() def __str__(self): return self.name class Meta: #app_label = 'app01' #由于该model连接default数据库,所以在此无需指定 ordering = ['name'] class tb2(models.Model): name=models.CharField(max_length=100,primary_key=True,unique=True) ip=models.GenericIPAddressField() rating = models.IntegerField() def __str__(self): return self.name class Meta: app_label = 'app02' ordering = ['name']
class mtable01(models.Model): name=models.CharField(max_length=100,primary_key=True,unique=True) ip=models.GenericIPAddressField() rating = models.IntegerField() def __str__(self): return self.name class Meta: app_label = 'app02' #定义该model的app_label ordering = ['name'] 使用migrate命令同步数据库: class tb06(models.Model): name=models.CharField(max_length=100,primary_key=True,unique=True,db_column='mycname') #使用db_column自定义字段名称 ip=models.GenericIPAddressField() rating = models.IntegerField() def __str__(self): return self.name class Meta: db_table = 'mytable' #自定义表名称为mytable verbose_name = '自定义名称' #指定在admin管理界面中显示的名称 app_label = 'app02' ordering = ['name']
migrate管理命令一次操作一个数据库。默认情况下,它在default 数据库上操作,但是通过提供一个 --database 参数,告诉migrate同步一个不同的数据库。
1)同步default节点数据库,只运行不带 --database参数的命令,不对其他数据库进行同步
python manage.py migrate
python manage.py makemigrations
python manage.py migrate
2)同步hvdb节点数据库:
python manage.py migrate --database=hvdb
python manage.py makemigrations
python manage.py migrate --database=hvdb
结果:
testdjango数据库(hvdb节点)下的app02_mtable01表对应app02下的mtable01模型
testdjango数据库(hvdb节点)下的app02_tb2表对应app01下的tb2模型
testly数据库(default节点)下的app01_tb05表对应app01下的tb05模型
实测
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME':'review_scrapy_dj', 'USER': 'xxxx', 'PASSWORD': 'xxxx', 'HOST': '1.1.1.39', 'PORT': '3306', 'CHARSET':'utf8', "COLLATION":'utf8_general_ci' }, "slave":{ 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db2.sqlite3'), } } DATABASE_ROUTERS = ['utils.myrouter.Router',] # router
# -*- coding:utf-8 -*- class Router: def db_for_read(self, model, **hints): return 'slave' def db_for_write(self, model, **hints): return 'default'
from django.db import models # Create your models here. class UserInfo(models.Model): name = models.CharField(max_length=64) gender_choices = [ (0, "男"), (1, "女"), ] gender = models.IntegerField(choices=gender_choices,null=True,default=None) class Role(models.Model): role = models.CharField(max_length=64) user = models.ManyToManyField(to="UserInfo")
from django.shortcuts import render,HttpResponse,redirect from django.views import View from . import models # Create your views here. class Index(View): def get(self,request): user_objs_read = models.UserInfo.objects.all() user_objs_default = models.UserInfo.objects.using("default").all() gender_choices = models.UserInfo.gender_choices context = { "user_objs_read":user_objs_read, "gender_choices":gender_choices, "user_objs_default":user_objs_default, } return render(request,"index.html",context=context) def post(self,request): name = request.POST.get("name") print(type(request.POST.get("gender"))) gender = int(request.POST.get("gender")) models.UserInfo.objects.create(name=name,gender=gender) return redirect("/test2db/")
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>index</h1> <div> <h3>user_objs_read</h3> {% for user_obj in user_objs_read %} <div>{{ user_obj.name }}——{{ user_obj.get_gender_display }}</div> {% endfor %} </div> <hr> <div> <h3>user_objs_write</h3> {% for foo in user_objs_default %} <div>{{ foo.name }}——{{ foo.get_gender_display }}</div> {% endfor %} </div> <hr> <form action="" method="post"> {% csrf_token %} <div>name<input type="text" name="name"></div> <div>gender <select name="gender" id=""> {% for gender_choice in gender_choices %} <option value="{{ gender_choice.0 }}">{{ gender_choice.1 }}</option> {% endfor %} </select> </div> <input type="submit"> </form> </body> </html>