公司一个项目要用 Flask 来写 Web 后台,数据库用的是 Oracle,我心想 Oracle 我从来没玩过,还是像 Django 那样用 ORM 来吧。Flask 虽然是个微框架,只干该干的事,但是有些插件其实是有类官方地位的,比如 Defacto ORM 就是 SQLAlchemy,结果我踩了不少「匪夷所思」的坑。

数据库链接 URL 的问题

SQLAlchemy 有个子模块可以根据数据库的一些属性来生成连接数据库的 URL。比如我们可以这样生成一个 URL:

from sqlalchemy.engine.url import URL

config = {
  "drivername": "oracle",
  "host": "127.0.0.1",
  "port": "1521"
  "database": "default",
  "username": "admin",
  "password": "admin",
}

db_uri = URL(**config)
# oracle://admin:admin@127.0.0.1:1521/default

问题是,用这么一个 URL,你是连不上 Oracle 数据库的,为什么?因为 Oracle 数据库有特殊性。正确的写法是:

oracle://admin:admin@127.0.0.1:1521/?service_name=default

事实上,Oracle 自己的官方 Python Binding,也就是 cxOracle,是不会这么生成 URL 的。

SQLAlchemy-Utils 判断数据库是否存在的问题

SQLAlchemy-Utils 是 SQLAlchemy 的一个知名周边,提供了一些非常方便的函数和数据类型,比如和初始化相关的一些函数。

其中有一个 database-exists 的函数,会判断指定的数据库是否存在,但是这个函数在 Oracle 下会报错。 原因是用来判断数据库是否存在的语句是 SELECT 1;,然而 Oracle 是不支持这个语句的,正确的写法应该是 SELECT 1 FROM DUAL,其中 DUAL 是 Oracle 数据库在安装过程中生成的默认表单。

抽象和细节的平衡

我们用 ORM 有一部分的理由是为了隐藏 SQL 细节,但是如果连第一步的正确执行都无法保证,还需要去深入了解 代码细节,那还不如从一开始就写 Raw SQL 吧。

另外,Flask 号称 Micro,但实践中我如果要快速搭建一个靠谱的 App,还是需要导入若干个有官方地位的扩展, 而这些扩展的文档,都不会和 Flask 本身有紧密的照应,可以说是支离破碎,对于我这种曾经用过 Django 的人来说, 很多东西可以猜到,但是对于初学者,怕并不是那么友好。