为什么在SQLAlchemy中有一个不绑定到引擎的MetaData对象有用?
我正在试着理解MySQL中关于MetaData
对象和engine
对象的行为。这个回答把MetaData
描述为
一组表的定义
而engine
则被描述为
特定数据库的方言和连接细节
到目前为止,这些解释都很好。但是,为什么要把这两者分开呢?难道表的定义不是和特定数据库有关联的吗?
2 个回答
其实,表的定义并不是和某个特定的数据库绑定在一起的。sqlalchemy 的元数据对象,以及所有相关的对象(比如表、列、索引等),都定义了一个完全抽象的结构,而不指向任何特定的数据库。引擎主要有三个部分:连接信息、方言和连接池。这里的方言是最重要的部分,它定义了如何连接到特定的数据库,更重要的是,它还定义了如何把 sqlalchemy 的抽象结构对象(元数据等)转换成适合那个数据库和驱动的 sql 命令。
这种分离有很多不同的用途:
应用程序可以在模块级别定义它们的结构,在模块被导入时创建表和元数据……但在应用程序真正运行之前不需要定义引擎(因为要知道完整的连接网址和方言,通常需要先读取配置文件或获取用户输入)。
你可能会遇到多个引擎与同一组元数据绑定的情况,比如一个网页应用,每个用户/密码用自己的凭证连接到数据库(虽然这样效率不高,但有时出于安全考虑是需要的)。由于元数据是一个全局对象,在这种情况下,把它绑定到特定的引擎就没有意义了。
虽然不常见,但你也可能会遇到反过来的情况,即一个引擎对应多个元数据实例。这种情况可能发生在多个子组件共享同一个数据库,但使用不同的表名时。此外,当你想把应用程序当前的结构与从服务器反映过来的另一个元数据实例进行比较(比如为了结构迁移)时,也可能会出现这种情况。这并不严格阻止你把它们都绑定到引擎,但确实有助于说明为什么可以有多个元数据或引擎实例。
可能还有其他一些用例我现在想不起来,但这些应该能让你大致明白为什么它们在概念上是分开的。
在SQLAlchemy 0.1版本的时候,没有“元数据”这个概念,Engine
是直接和每个Table
绑定在一起的。这个做法很快就显得过时了,部分原因是大家希望在连接之前就能声明他们的表对象,于是“绑定元数据”这个概念就出现了。接着,大家就陷入了一段时间的混乱中。因为我之前告诉他们这样做,所以很多人习惯性地使用以下方式:
table.insert().execute()
result = table.select().execute()
也就是说,没有事务,每次都用一个新的连接。然后我们会说,“哦,其实你应该使用事务,这样连接会更高效”,接着就基本上告诉他们需要重写他们的应用程序,这让“sqlalchemy有太多方式”的说法像气球一样膨胀开来。
与此同时,Pylons和其他早期的WSGI框架也在努力让多个“应用”同时运行——在某些情况下,不同的“用户”会在不同的数据库中各自拥有一套表。更常见的是水平扩展的方法,即相同的表存在于多个数据库中。我这里有一个应用,它内置了一个“复制”系统,记录会定期从“主”数据库复制到“历史”数据库,并且那里的表元数据也是共享的。
关键是,对于所有这些使用场景,用户来到SQLAlchemy时,他们的理解都是从“绑定元数据”开始的。很多博客和教程都在使用这个概念。而且相当一部分用户需要跳出这个系统,结果却完全搞糊涂了,因为他们发现还有一种“其他方式”可以工作。因此,显然“绑定元数据”这个系统作为默认设置太过僵化。理想情况下,我希望我根本就没有实现它,我自己也从来不使用它。这就是为什么现在相关文档被放在一个单独的部分,新用户如果只是随便浏览文档,往往会增加邮件列表的支持负担,他们找不到这些内容,也不会感到困惑。这个部分本身有很多要点,清楚地解释了在什么情况下以及为什么它会令人困惑。我假设你已经阅读过它,链接在这里:http://www.sqlalchemy.org/docs/core/schema.html#binding-metadata-to-an-engine-or-connection。