Updated August, 2014 – the approach here really won’t work very well if you are using transactions! See the notes inline.

20148月更新 –如果您使用交易,此处的方法实际上将无法很好地工作! 请参阅内联注释。

As luck would have it, I’m currently assigned to work with some existing Django code. It’s been quite a long time I’ve gone without needing to do so, but now that I’m there, I can start fleshing out how some of the use cases for Django can be approximated in SQLAlchemy.

幸运的是,我目前被分配使用一些现有的Django代码。 我已经走了很长时间了,而无需这样做,但是现在我已经在那里,我可以开始充实如何在SQLAlchemy中近似使用Django的一些用例了。

Today it’s something that’s really quite simple – how to mimic the Multiple Databases example in Django’s docs. The Django approach is to build a “router” object into the system which can then decide on a query-by-query basis which of several databases should be used for each query. For this, they provide an interface which includes db_for_read(), db_for_write(), allow_relation() and allow_syncdb(). In particular, db_for_read() and db_for_write() are used when emitting most SQL. These receive an argument called model, which the docs state is a “type”, i.e. a class. Because the database determination is based on type, we call this in SQLAlchemy vertical partitioning – databases are partitioned along types.

今天,这真的很简单-如何模仿Django文档中的Multiple Databases示例。 Django的方法是在系统中构建一个“ router”对象,然后可以在逐个查询的基础上决定每个查询应使用几个数据库中的哪个。 为此,它们提供了一个接口,其中包括db_for_read()db_for_write()allow_relation()allow_syncdb() 。 特别是,在发出大多数SQL时使用db_for_read()db_for_write() 。 它们接收一个称为model的参数,docs声明该状态为“类型”,即类。 因为数据库确定是基于类型的,所以我们在SQLAlchemy垂直分区中称其为“数据库”-沿类型进行分区。

The specific example here has the following behavior:

这里的特定示例具有以下行为:

  1. Write operations occur on a database referred to as leader.
  2. Read operations are split among two different databases referred to as follower1 and follower2.
  3. Operations for a specific subset of classes denoted by myapp occur on a database referred to as other.
  1. 写操作发生在称为Leader的数据库上。
  2. 读取操作分为两个不同的数据库,分别称为follower1follower2
  3. myapp表示的特定类子集的操作发生在称为other的数据库上。

When using SQLAlchemy, we of course don’t have the concept of “apps” as a delineating factor between different types. So we’ll use the old fashioned approach and just use the type itself, that is, what hierarchy it inherits from, to determine which subsystem of the application it heralds from.

当使用SQLAlchemy时,我们当然没有“应用程序”的概念作为不同类型之间的描述因素。 因此,我们将使用老式的方法,而仅使用类型本身(即,它继承自哪个层次结构)来确定从哪个子系统继承。

The SQLAlchemy Session makes all decisions about what Engine to use within the get_bind() method. This method is given pretty much the same information that db_for_read() and db_for_write() receive, where a Mapper is passed in, if available, and we can also check if the operation is a “write” just by checking if the Session is flushing.

SQLAlchemy 会话对在get_bind()方法中使用哪种引擎做出所有决策。 此方法获得的信息几乎与db_for_read()db_for_write()所接收的信息相同,在该信息中传递了Mapper (如果有的话),我们还可以仅通过检查Session是否刷新来检查操作是否为“写入” 。

进口货 (Imports)

We’ll build up our example in the usual way, as a full script broken out. First the imports:

作为完整的脚本,我们将以通常的方式构建示例。 首先进口:

from from sqlalchemy sqlalchemy import import ColumnColumn , , IntegerInteger , , StringString , , MetaDataMetaData , , create_engine
create_engine
from from sqlalchemy.orm sqlalchemy.orm import import scoped_sessionscoped_session , , sessionmakersessionmaker , , Session
Session
from from sqlalchemy.ext.declarative sqlalchemy.ext.declarative import import declarative_base
declarative_base
import import random
random
import import shutil
shutil

引擎注册表 (Engine Registry)

Then, some Engine instances. Note that, unlike Django, SQLAlchemy is not a framework, and doesn’t have a settings.py file or an existing registry of engines. Assuming we can decide on a decent place to put our own registry, we just stick them in a dict:

然后,一些引擎实例。 请注意,与Django不同,SQLAlchemy不是框架,也没有settings.py文件或现有的引擎注册表。 假设我们可以决定在一个合适的地方放置自己的注册表,只需将它们放在字典中即可

The example here uses SQLite databases, so that it’s quick and easy to actually run the script. However, as I’m not familiar with replication functionality in SQLite, we’ll have to “fake” the part where “leader” replicates to “follower”, just for the sake of example.

此处的示例使用SQLite数据库,因此可以快速,轻松地实际运行脚本。 但是,由于我不熟悉SQLite中的复制功能,因此,仅出于示例的目的,我们必须“伪造”“领导者”复制到“跟随者”的部分。

路由 (Routing)

Next comes our implementation of get_bind(). We’re building into a more open-ended space here, which can good or bad thing, depending on where you’re coming from. Instead of building two “router” classes with two db routing methods each, we just need to make one method with some conditionals:

接下来是我们对get_bind()的实现 。 我们正在这里建立一个更开放的空间,根据您来自哪里,这是好是坏。 与其构建每个带有两个数据库路由方法的两个“路由器”类,我们只需要使一个方法具有一些条件:

class class RoutingSessionRoutingSession (( SessionSession ):):def def get_bindget_bind (( selfself , , mappermapper == NoneNone , , clauseclause == NoneNone ):):if if mapper mapper and and issubclassissubclass (( mappermapper .. class_class_ , , OtherBaseOtherBase ):):return return enginesengines [[ 'other''other' ]]elif elif selfself .. _flushing_flushing ::return return enginesengines [[ 'leader''leader' ]]elseelse ::return return enginesengines [[randomrandom .. choicechoice ([([ 'follower1''follower1' , , 'follower2''follower2' ])])]
]

So above, we use a three-state conditional to make exactly those same choices the Django example does. When we want to detect the classes destined for the “other” engine, we look for a particular base class called OtherBase. We could just as easily do other kinds of checks here, such as checking for a particular attribute on the class.

因此,在上面,我们使用三态条件来做出与Django示例相同的选择。 当我们要检测发往“其他”引擎的类时,我们会寻找一个名为OtherBase的特定基类。 我们可以在这里轻松进行其他类型的检查,例如检查类中的特定属性。

We also look at a state variable called _flushing to determine operations destined for the leader. The Session._flushing flag is set as soon as the flush procedure begins, and remains on until the method completes. SQLAlchemy uses this flag mainly to prevent re-entrant calls to autoflush when it’s already inside of the flush process.

我们还将查看一个名为_flushing的状态变量,以确定发往该领导者的操作Session._flushing标志在刷新过程开始时立即设置,并保持打开状态,直到方法完成。 SQLAlchemy使用此标志主要是为了防止重新进入自动刷新的调用(当它已经在刷新过程中时)。

That’s all we need in the way of Session, for the moment. To wire this Session up into the standard scoped_session(sessionmaker(autocommit=True)) process, we can pass it into sessionmaker():

目前,这就是我们需要的Session方式。 要将此Session连接到标准scoped_session(sessionmaker(autocommit = True))流程中,我们可以将其传递到sessionmaker()中

Note also that, with some dependency on the replication system we have in place, using this approach may very well mean we can’t use a transaction for our normal work. If we write some rows into the leader, and don’t commit our transaction, we won’t see those rows if we try to SELECT from the follower because the transaction is isolated from any other node until committed. Hence the session is set up with autocommit=True, which uses an ad-hoc transaction for each SELECT statement, and one for each flush operation.

还请注意,由于对现有复制系统有一定的依赖性,因此使用此方法可能很可能意味着我们不能在正常工作中使用事务 。 如果我们向领导者写一些行,并且不提交事务,那么如果我们尝试从跟随者中进行SELECT,则不会看到这些行,因为在提交之前,该事务与任何其他节点都是隔离的。 因此,使用autocommit = True设置会话,该会话对每个SELECT语句使用临时事务,对每个刷新操作使用一个临时事务。

模型设定 (Model Setup)

Next, let’s build up the “model”. This is the part where we need to come up with an answer for Django’s syncdb feature, which of course in SQLAlchemy is MetaData.create_all(). We now have two different schemas – one schema is shared between leader, follower1, and follower2, the other is destined for other. We’ll split out our table metadata among these backends using two different MetaData() objects. To achieve that transparently, I’ll use a trick I don’t think people are quite aware of yet, which is to use abstract declarative bases.

接下来,让我们建立“模型”。 这是我们需要为Django的syncdb功能提供答案的部分,在SQLAlchemy中,当然是MetaData.create_all() 。 现在,我们有两种不同的模式–一种模式在Leaderfollower1follower2之间共享,另一种模式供其他模式使用 。 我们将使用两个不同的MetaData()对象在这些后端中拆分表元数据 。 为了透明地实现这一点,我将使用一个我认为人们还不太了解的技巧,即使用抽象的声明性基础。

Starting with a useful declarative base that will give us an id and a data column, as well as a decent __repr__():

从一个有用的声明性基础开始,它将为我们提供一个id和一个data列,以及一个不错的__repr __()

class class BaseBase (( objectobject ):):id id = = ColumnColumn (( IntegerInteger , , primary_keyprimary_key == TrueTrue ))data data = = ColumnColumn (( StringString (( 5050 ))))def def __repr____repr__ (( selfself ):):return return "" %s%s (id=(id= %r%r , data=, data= %r%r )" )" % % ((selfself .. __class____class__ .. __name____name__ ,,selfself .. idid , , selfself .. datadata))Base Base = = declarative_basedeclarative_base (( clscls == BaseBase )
)

We then split out Base into two subtypes, which won’t be mapped. To tell declarative not to map these non-mixin, direct descendants of Base, we use a fairly new flag called __abstract__:

然后,我们将Base分为两个子类型,它们不会被映射。 为了告诉声明者不要映射Base的这些非混合直接后代,我们使用了一个相当新的标志__abstract__

and holy crap look at that a MetaData on each one! You can do that? Sure can – the classes we build on top of DefaultBase will put the Table objects into one MetaData, the classes we built on top of OtherBase will put them into another. We’ve basically just replaced the .metadata attribute declarative sets up with our own – there’s no magic. The DefaultBase and OtherBase classes are also not mapped and are transparent to the mapping mechanism.

和神圣的废话看看每个元数据 ! 你能做到吗? 当然可以–我们在DefaultBase之上构建的类会将Table对象放入一个MetaData中 ,我们在OtherBase之上构建的类会将它们放入另一个MetaData中 。 我们基本上只是用我们自己的.metadata属性声明式设置替换了–没有魔术。 DefaultBaseOtherBase类也未映射,并且对于映射机制是透明的。

Let’s build out three “models” (I really prefer to say “class”, let’s see if I can get to the end of this post without complaining more about it…):

让我们建立三个“模型”(我真的更喜欢说“类”,让我们看看我是否能在不抱怨的情况下结束本文的结尾……):

class class Model1Model1 (( DefaultBaseDefaultBase ):):__tablename__ __tablename__ = = 'model1''model1'class class Model2Model2 (( DefaultBaseDefaultBase ):):__tablename__ __tablename__ = = 'model2''model2'class class Model3Model3 (( OtherBaseOtherBase ):):__tablename__ __tablename__ = = 'model3'
'model3'

Bang. For those unfamiliar with declarative, because these objects ultimately descend from Base above, they will have an id and a data column available, where id is a surrogate primary key.

砰。 对于那些不熟悉声明式语言的人,因为这些对象最终来自上方的Base ,所以它们将具有一个id和一个数据列,其中id是代理主键。

表格创建 (Table Creation)

We use MetaData.create_all() here. Anything that’s DefaultBase should have tables created in leader, follower1, follower2:

我们在这里使用MetaData.create_all()DefaultBase的任何内容都应在Leaderfollower1follower2中创建表:

OtherBase then goes to other:

然后OtherBase转到其他

OtherBaseOtherBase .. metadatametadata .. create_allcreate_all (( enginesengines [[ 'other''other' ])
])

用法 (Usage)

We now have the tables built, we can show off things getting sent to leader within an explicit transaction block:

现在我们已经建立了表,我们可以展示在明确的交易块中发送给领导者的东西:

If we have SQL echoing on, we’d see transactions starting on each of leader and other, the requisite INSERT statements, then the two COMMIT calls. Note that the BEGIN for other doesn’t occur until the unit of work actually comes across SQL destined for this engine:

如果我们启用了SQL回显功能,则将看到事务从leaderother (必需的INSERT语句)的每个开始,然后是两个COMMIT调用。 请注意,直到工作单元实际遇到发往该引擎SQL时,针对其他对象BEGIN才发生:

INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader BEGIN BEGIN (( implicitimplicit )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader INSERT INSERT INTO INTO model1 model1 (( datadata ) ) VALUES VALUES (( ?? )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader (( 'm1_a''m1_a' ,)
,)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader INSERT INSERT INTO INTO model1 model1 (( datadata ) ) VALUES VALUES (( ?? )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader (( 'm1_b''m1_b' ,)
,)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other BEGIN BEGIN (( implicitimplicit )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other INSERT INSERT INTO INTO model3 model3 (( datadata ) ) VALUES VALUES (( ?? )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other (( 'm3_a''m3_a' ,)
,)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other INSERT INSERT INTO INTO model3 model3 (( datadata ) ) VALUES VALUES (( ?? )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other (( 'm3_b''m3_b' ,)
,)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader INSERT INSERT INTO INTO model2 model2 (( datadata ) ) VALUES VALUES (( ?? )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader (( 'm2_a''m2_a' ,)
,)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader INSERT INSERT INTO INTO model2 model2 (( datadata ) ) VALUES VALUES (( ?? )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader (( 'm2_b''m2_b' ,)
,)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other COMMIT
COMMIT
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. leader leader COMMIT
COMMIT

Now let’s query. Normally, if we had a Postgresql or MySQL replication environment set up, the data we write to leader would have been copied out to follower1 and follower2. So cover your eyes for a moment while we pretend:

现在让我们查询。 通常,如果我们设置了Postgresql或MySQL复制环境,那么我们写给Leader的数据将被复制到follower1follower2 。 因此,在我们假装时遮住您的眼睛一会儿:

(note that SQLAlchemy as of 0.7 doesn’t use a connection pool by default when it talks to SQLite – it just opens from the file each time. As long as nobody’s talking to the database, we can swap out the file).

(请注意,SQLAlchemy自0.7起,在与SQLite通讯时默认不使用连接池-每次都从文件打开。只要没有人在与数据库通讯,我们都可以换出文件)。

We can do some querying – SQL echoing is displayed inline here:

我们可以进行一些查询-SQL回显在此处内联显示:

>>> >>> print print ss .. queryquery (( Model1Model1 )) .. allall ()
()
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. follower2 follower2 BEGIN BEGIN (( implicitimplicit )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. follower2 follower2 SELECT SELECT model1model1 .. id id AS AS model1_idmodel1_id , , model1model1 .. data data AS AS model1_data
model1_data
FROM FROM model1
model1
[[ Model1Model1 (( idid == 11 , , datadata == 'm1_a''m1_a' ), ), Model1Model1 (( idid == 22 , , datadata == 'm1_b''m1_b' )])]>>> >>> print print ss .. queryquery (( Model2Model2 )) .. allall ()
()
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. follower1 follower1 BEGIN BEGIN (( implicitimplicit )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. follower1 follower1 SELECT SELECT model2model2 .. id id AS AS model2_idmodel2_id , , model2model2 .. data data AS AS model2_data
model2_data
FROM FROM model2
model2
[[ Model2Model2 (( idid == 11 , , datadata == 'm2_a''m2_a' ), ), Model2Model2 (( idid == 22 , , datadata == 'm2_b''m2_b' )])]>>> >>> print print ss .. queryquery (( Model3Model3 )) .. allall ()
()
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other BEGIN BEGIN (( implicitimplicit )
)
INFO INFO sqlalchemysqlalchemy .. engineengine .. basebase .. EngineEngine .. other other SELECT SELECT model3model3 .. id id AS AS model3_idmodel3_id , , model3model3 .. data data AS AS model3_data
model3_data
FROM FROM model3
model3
[[ Model3Model3 (( idid == 11 , , datadata == 'm3_a''m3_a' ), ), Model3Model3 (( idid == 22 , , datadata == 'm3_b''m3_b' )]
)]

手动访问 (Manual Access)

Django also includes a “manual” selection mode via the using() modifier on the QuerySet object. Let’s illustrate a modified version of our RoutingSession with a similar method added. We add one more check in get_bind(), to look for a local attribute called name – then we build a quick “generative” method that copies the state from one RoutingSession into another, so that the second session shares the same state:

Django还通过QuerySet对象上的using()修饰符包括“手动”选择模式。 让我们说明一个添加了类似方法的RoutingSession的修改版本。 我们在get_bind()添加一个检查,以查找名为name的本地属性,然后构建一个快速的“生成”方法,该方法将状态从一个RoutingSession复制到另一个RoutingSession中 ,以便第二个会话共享相同的状态:

So assuming we plugged in the above Session, we can explicitly query the leader as:

因此,假设我们插入了上述Session ,我们可以显式查询领导者

m1 m1 = = SessionSession ()() .. using_bindusing_bind (( "leader""leader" )) .. queryquery (( Model1Model1 )) .. firstfirst ()
()

Admittedly, the using_bind() method above might be something I should add some helpers for, like a @generative decorator similar to that available for Query, so that the vars.update() approach is not needed.

诚然,上面的using_bind()方法可能是我应该为其添加一些帮助器的东西,例如类似于Query可用的@generative装饰器,因此不需要vars.update()方法。

I hope this example is useful, and sheds some light on how we do things in SQLAlchemy.

我希望该示例有用,并阐明我们如何在SQLAlchemy中进行操作。

Download the example: sqlalchemy_multiple_dbs.py

下载示例: sqlalchemy_multiple_dbs.py

翻译自: https://www.pybloggers.com/2012/01/django-style-database-routers-in-sqlalchemy/

SQLAlchemy中的Django风格的数据库路由器相关推荐

  1. Python3.7中,Django配置MySql数据库

    上一节中讲了Python3.7搭建Django框架项目 ,并且项目能够运行起来,Django框架中默认配置的数据库是sqlite,sqlite数据库虽然小巧,但是对于大型项目时sqlite就会有瓶颈, ...

  2. HTML实现选择数据库字段,django项目中在后台获取了数据库的某一列,如何将其显示在html模板中的select标签内的option选项下?...

    问题描述 不好意的整序大作站对近从体的家为宽应近从体的家思,本人是一个初学者,在尝试用django制作网站的时候遇到了一个问题,网上搜索了很久,苦于没有答案,想请教一下.想要实现的是在前端html里有 ...

  3. DATABASE_ROUTERS在Django中使用多个MySQL数据库进行配置

    在Django中,默认使用的MySQL数据库为default, 如果不进行配置的话,迁移建表就会创建到default数据库 准备: 先创建两个数据库 在创建一个管理员管理这两个库 create use ...

  4. Django动态获取mysql连接,django model中的choices 动态从数据库中获取

    django model中的choices 动态从数据库中获取 例如 model中,有一个type字段 type = models.CharField(max_length=255, default= ...

  5. python软件是干什么用的-python中的django是做什么的

    Django是什么? Django是一个基于Python的Web应用框架.它与Python的另外一个Web 框架 Flask最大的区别是,它奉行 "包含一切" 的哲学.该理念即为: ...

  6. Django——多个数据库

    Django--多个数据库 官方文档:https://docs.djangoproject.com/en/3.2/topics/db/multi-db/ 主要介绍Django 对与多个数据库交互的支持 ...

  7. 第五章:Django连接mysql数据库

    一.数据库的选择 不管是什么后端语言都需要跟数据打交道,对数据的增删改查等操作,Django有自己的一套ORM操作数据库的方式 1.Django默认的数据库是sqlite 2.一般我们常用ORM对my ...

  8. django连接mysql数据库_Django学习笔记(4)——Django连接数据库

    前言 在MVC或者MTV设计模式中,模型(M)代表对数据库的操作.那么如何操作数据库呢?本小节就认真学习一下.首先复习一下Django的整个实现流程 ,然后再实现一下使用数据库的整个流程,最后学习一下 ...

  9. django引入现有数据库

    Django引入外部数据库还是比较方便的,步骤如下: 1.创建一个项目,修改seting文件,在setting里面设置你要连接的数据库类型和连接名称,地址之类,和创建新项目的时候一致. 2.运行下面代 ...

最新文章

  1. HDU1040简单排序题
  2. BFS HDOJ 1242 Rescue
  3. 攻防世界web新手区解题 /cookie / disabled_button / weak_auth
  4. [深度学习]-CNN-ImageNet历年冠军和相关CNN模型
  5. G20:奥巴马称美国拥有全球最大最好的网络武器库
  6. IE各浏览器HACK
  7. mysql表不存在但实际存在_历史上有哪些实际上并不存在的人物但很多人相信他存在的?...
  8. 今日学习在线编程题:可怜的小码哥
  9. 关于PLC控制系统中电动机过载保护方法的探究
  10. 企业上云要几步?中拓互联奉送企业上云全攻略
  11. 目录操作的相关API 和 获取文件的属性信息
  12. linux 5g,5G的时隙配置
  13. Win11电脑外接显卡拓展坞后蓝屏怎么办?
  14. layui实现导航栏目
  15. lisp语言与python_又要头秃?2020 年七大 AI 编程语言大盘点
  16. 电脑怎么查看计算机历史记录,win7电脑使用记录怎么查询_win7电脑使用历史记录如何查看-win7之家...
  17. 数学金字塔C语言原函数,文华转金字塔 ← 金字塔软件问题提交 ← 金字塔客服中心 - 专业程序化交易软件提供商...
  18. 通用查询实现方案(可用于DDD)[附源码] -- 简介
  19. 不坑盒子 + 智能写作(Office、WPS插件)助你高效办公,早点下班回家。
  20. 计算机辅助药物设计 fda,计算机辅助药物设计中的分子动力学模拟.pdf

热门文章

  1. 未来已来!阿里小蜜AI技术揭秘
  2. 【游戏测评】《海盗来了》:四手终与双拳难辨
  3. 分布式爬虫联系项目1–阳光热线网站的分布式爬取
  4. 基于XBee3 zigbee Micropython编程指南
  5. matlab里伽马校正的特点,查表法实现gamma校正的matlab仿真模型源码
  6. Golang中AK/SK认证的实现
  7. pscs6免激活版,
  8. instrument Time Profiler总结
  9. OpenCV常用函数极简简介
  10. 帝国CMS仿玩游戏网源码大型游戏资讯网站源码