OpenStack的數(shù)據(jù)庫開發(fā)基礎 - SQLAlchemy
前言
對于一個業(yè)務系統(tǒng),如何高效、平穩(wěn)地使用數(shù)據(jù)庫是每一個開發(fā)人員都會遇到的問題,OpenStack 也不例外,以 OpenStack 的虛擬網(wǎng)絡組件 Neutron 為例,其數(shù)據(jù)庫涉及幾百張表,需要維護數(shù)據(jù)庫版本近百;一些表因為設計原因形成了很高的“熱點”;因為 OpenStack 是分布式的,需要以***小一點的代價保證操作時的一致性……最重要的是,每個人的數(shù)據(jù)庫水平都不一樣,怎么保證整個開源社區(qū)數(shù)百名提交者有一樣的數(shù)據(jù)庫操作風格,如何維護這些代碼?
OpenStack 做為一個完全使用 Python 開發(fā)的項目,利用已有的豐富模塊是開發(fā)時重要的中心思想之一,同時為了便于整個社區(qū)幾百名背景不同水平不同的開發(fā)者協(xié)作,最終選擇了 SQLAlchemy 和 Alembic 作為數(shù)據(jù)庫開發(fā)的基礎。
Why SQLAlchemy
在回答為什么使用 SQLAlchemy 之前,我們先盤點一下目前 Python 能用的 ORM 庫,因為挑一個庫在很大程度上實在挑社區(qū),所以我把***版的 release 時間也寫出來:
Storm:***版 0.20,release 于 2013 年,開發(fā)已經(jīng)比較沉寂……對外鍵的更新、刪除要求比較奇怪。
SQLObject:***版 1.7.3,release 于 2014.12.18,開發(fā)歷史久,目前活躍度不是很高。
Django’s ORM:來自于 Django,Django 內(nèi)置,使用 Django 開發(fā)的話會很方便,但它不能脫離 Django 運行,也不能處理一些復雜的請求。
peewee:***版 2.4.4 發(fā)布于2014.12.3,輕量方便,內(nèi)置 SQLite、MySQL和PostgreSQL的支持。
PonyORM:***版 0.6,release 于 2014.11.5。使用 AGPL 許可。有圖形化的編輯器。非為大型應用設計。
SQLAlchemy:***版 0.9.8,release 于 2014.10.13,企業(yè)級 API,設計靈活。加入了一些自己的概念,學習曲線較高。
總結(jié)一下,Storm 曾經(jīng)應用比較廣泛,但現(xiàn)在社區(qū)不再活躍,很難保證將來遇到問題能否交給社區(qū)解決,而且 Storm 對數(shù)據(jù)庫架構(gòu)同步處理的比較奇怪,還有頻繁產(chǎn)生 DDL 操作 造成庫級鎖這些問題無法讓人放心;SQLObject 也是一個很出名的 ORM 庫,但與 SQLAlchemy 相比,后者效率更高,對一些高級特性的支持不如后者。
SQLAlchemy 的架構(gòu)
- Summary
SQLAlchemy 很有特色的一點就是它刻意被分為另種用法,就是 CORE 和 ORM,這是由它的架構(gòu)決定的。
這樣的架構(gòu)的好處是帶來了 Core 與 ORM 的解耦和,當我們需要高性能的 SQL 執(zhí)行但又不想拋棄 SQLAlchemy 帶來的session管理、連接池管理、數(shù)據(jù)庫“中立”的語句編寫等這些好處時我們可以直接用 CORE。直接用 CORE 是什么意思呢?我們看到架構(gòu)里只有Rational Mapper在 CORE 之上,實際也確實如此,因為Schema、SQL Expression Language還在 CORE 內(nèi),所以使用 CORE 可以直接寫純 SQL 語句,我們稱之為Raw SQL的寫法,也可以用SQL Expression,后者因為是相當于寫 Python 代碼,所以可以帶來更好地閱讀性和可維護性,不過Raw SQL更靈活,所以在很復雜的語句面前Raw SQL就更占優(yōu)勢了。
再往下看這個圖,我們可以看到 DBAPI 是由Third party libraries實現(xiàn)的,也就是說 SQLAlchemy 并沒有提供直接連接數(shù)據(jù)庫的功能,而是通過第三方實現(xiàn):
SQLalchemy 對dialect支持很全,就以常見的 MySQL 為例,可以支持:MySQL-Python、OurSQL、PyMySQL、MySQL Connector/Python、CyMySQL、Google Cloud SQL、PyODBC、zxjdbc for Jython,具體可以在 SQAlchemy 的dialects頁面里查到。
這樣有什么壞處呢,最明顯的就是低效。因為傳統(tǒng) Python 解釋器 CPython 的實現(xiàn)原因(主要是 C 的問題)長的函數(shù)調(diào)用棧會帶來顯著地性能問題。 由于路徑過長,不可避免地導致運行時的緩慢。SQLAlchemy 花了很舊去縮短調(diào)用路徑和通過 C 代碼處理性能瓶頸,效果還不錯,不過***還是希望 PyPy 能夠廣泛流行起來,通過JIT緩解這個問題。
- Engine
上面的圖還是一張抽象程度比較高的,下面我細節(jié)點的介紹下 SQLAlchemy 的Engine。
對于使用者來說,Engine是核心,因為Connection、 ResultProxy這些都是在Engine之后生成的,建立Engine則有兩個重點,就是Pool和Dialect,前者是做連接池管理,后者則負 責與 DBAPI 的溝通,如同其名字所示,負責“方言”與“普通話”的翻譯。上圖是以psycopg2為例的,使用 MySQL(PyODBC)也是類似的。
通過Dialect和ExecutionContext向開發(fā)者提供了一致的接 口,前者處理了數(shù)據(jù)庫的特性,比如使用 PostgreSQL 數(shù)據(jù)庫其 Array 數(shù)據(jù)類型、schema、catalog等,后者處理psycopg2 DBAPI 的用法,比如 unicode 字符處理、服務端 cursor 的行為這些。
所以說,DBAPI中的cursor在 SQLAlchemy 中會被包裝成ExecutionContext和ResultProxy來使用的。
- Schema
當數(shù)據(jù)庫的連接和交互處理完了,下一步就是提供非特定的表、字段的建立和操作方法。我們需要首先定義在數(shù)據(jù)庫中的表和字段的定義,及他們之間的關系, 也就是 Schema。對于數(shù)據(jù)庫的使用來說,最基本的至少要有兩個元素,那就是Table和Column,SQLAlchemy 使用了這兩個名字來描述表和字段。多個Column組合成Table,然后一些 Table構(gòu)成MetaData。Schema的結(jié)構(gòu)設計主要來自于 Martin Fowler 撰寫的 Patterns of Enterprise Application Architecture。
此外,Table和Column同時繼承自sqlalchemy.schema 和sqlalchemy.sql,使用時既可以在 ORM 的方式中使用,也可以以 SQL Expression Language 使用。在下圖中我們可以看到Table從sqlalchemy.sql中“可以select from”的類繼承,Coloumn從“可以用在 SQL expression”的類繼承。
- 表達式樹
SQLAlchemy 可以生成結(jié)構(gòu)豐富的各種語句,這是一個詞法分析樹,核心結(jié)構(gòu)是ClauseElement。
在 Python 中,得益于其 Magic Method,我們可以用__eq__、__ne__、__le__、__lt__、__add__、__mul__方便的重載運算符。以 Column 為對象的運算符由一個 mixin 類ColumnOperators實現(xiàn)重載。
編譯
在這里,編譯指生成 SQL 語句,主要由Compiled類完成,這個類有兩個核心的子類,SQLComplier和DDLCompiler。SQLComplier負責像 SELECT、INSERT、UPDATE、DELETE這些統(tǒng)稱為DQL (data query language) 和 DML (data manipulation language)的操作符的渲染,DDLCompiler負責CREATE和DROP,一般稱為 DDL。此外,還有一個類TypeCompiler處理某些數(shù)據(jù)庫的特殊語法。
Compiled的子類定以了一系列的 visit 開頭的方法,每一個都源于一個ClauseElement的特定子類。然后Compiled對象維護名字、結(jié)合參數(shù)和子查詢,最終是為了生成一個 SQL 查詢語句。
Migration
我們希望能像管理代碼一樣管理數(shù)據(jù)庫,可以像 git 一樣給數(shù)據(jù)庫定義版本、升/降級、打標簽,可以么?答案就是 Alembic。
Alembic 的作者與 SQLAlchemy 是同一人,使用起來有點像簡化版的 git,在 db 目錄里執(zhí)行 init,就可以自動生成基本結(jié)構(gòu)和配置文件。配置妥當后使用 alembic 可以生成一個數(shù)據(jù)庫模版,作為這個“版本”的數(shù)據(jù)升/降級文件,SQLAlchemy 會自動生成其“版本號”和歷史關系我們所需要做的便只是用調(diào)用 SQLAlchemy 和 Alembic 提供的 sa 和 op 定義數(shù)據(jù)庫表即可。
有同學可能問我在 SQLAlchemy 上做過一模一樣的定義了,是不是能不要讓我重復勞動啊?或者在我給 SQLAlchemy 做完修改后 Alembic 能不能自動“感知”到這些修改然后自己生成版本文件啊?答案是可以的,配置好元數(shù)據(jù)來源后,Alembic 可以用–autogenerate自動生成相應的版本文件。