經過demo學習OpenStack開發所需的基礎知識 -- 數據庫(1)

OpenStack中的關係型數據庫應用

OpenStack中的數據庫應用主要是關係型數據庫,主要使用的是MySQL數據庫。固然也有一些NoSQL的應用,好比Ceilometer項目。就SQL數據庫自己的應用而言,OpenStack的項目和其餘項目並無什麼區別,也是採用ORM技術對數據進行增刪改查而已。html

本文的重點是講解OpenStack項目中對關係型數據庫的應用的基礎知識,更多的是涉及ORM庫的使用。對於數據庫的安裝和配置,須要讀者本身查找一下MySQL的教程,若是隻是爲了驗證ORM的相關知識,也可使用sqlite數據庫。python

數據庫的選擇

OpenStack官方推薦的保存生產數據的是MySQL數據庫,在devstack項目(這個項目用於快速搭建OpenStack開發環境)中也是安裝了MySQL數據庫。不過,由於OpenStack的項目中沒有使用特定的只有在MySQL上才能用的功能,並且所採用的ORM庫SQLAlchemy也支持多種數據庫,因此理論上選擇PostgreSQL之類的數據庫來替代MySQL也是可行的。mysql

另外,OpenStack項目在單元測試中使用的是sqlite的內存數據庫,這樣開發者運行單元測試的時候不須要安裝和配置複雜的MySQL數據庫,只要安裝好sqlite3就能夠了。並且,數據庫是保存在內存中的,會提升單元測試的速度。web

ORM的選擇

什麼是ORM

ORM的全稱是Object-Relational Mapping,即對象關係映射,是一種利用編程語言的對象來表示關係數據庫中的數據的技術,其更形式化的定義能夠參考Wiki頁面Orject-relational mapping。簡單的說,ORM就是把數據庫的一張表和編程語言中的一個對象對應起來,這樣咱們在編程語言中操做一個對象的時候,實際上就是在操做這張表,ORM(通常是一個庫)負責把咱們對一個對象的操做轉換成對數據庫的操做。sql

Python中的ORM實現

通常來講,各類主流語言都有本身的ORM實現,通常來講也不僅一種,比較出名的有Java的Hibernate,Ruby on Rails的ORM,C++的ODB等。在Python中也存在多種ORM的實現,最著名的兩種是Django的Model層的ORM實現,以及SQLAlchemy庫。這兩種ORM實現基本上是Python中ORM的事實上的標準,若是你寫Django應用,那麼你就用Django自帶的實現;否則,你就能夠選擇SQLAlchemy庫。數據庫

OpenStack基本上都是Python項目,因此在OpenStack中,ORM主要是使用了SQLAlchemy庫(Keystone, Nova, Neutron等);不過使用了Django的Horizon項目(面板)仍是使用了Django自帶的ORM實現。本文主要是講解OpenStack中如何使用SQLAlchemy庫,這個也是開發OpenStack項目的最基本知識。編程

SQLAlchemy

SQLAlchemy簡介

SQLAlchemy項目是Python中最著名的ORM實現,不只在Python項目中也獲得了普遍的應用,並且對其餘語言的ORM有很大的影響。OpenStack一開始選擇這個庫,也是看中了它足夠穩定、足夠強大的特色。bash

SQLAlchemy項目的官網是http://www.sqlalchemy.org/,目前該項目最新的版本是1.0.111.0系列是今年剛發的,0.9系列應該仍是應用最普遍的版本。對於通常的應用來講,0.9系列和1.0系列差異不大。session

關於SQLAlchemy的學習

我我的以爲SQLAlchemy的學習難度會比Django的Model層難一些,由於一個最簡單的例子也會有一些不太直觀的地方,對於沒用過的人來講,會比較難以理解。不過SQLAlchemy官網整理了一些比較不錯的入門教程,是一個比較好的學習起點:Tutorials。另外,官方的Reference實際上是一個很好的教程,講了不少基本的概念,有助於理解SQLAlchemy的庫的使用。Reference的地址是:http://docs.sqlalchemy.org/en/rel_1_0/,還能夠直接下載PDF版本。我我的建議你們直接閱讀Reference便可,閱讀順序就按照PDF文件的章節編排順序進行。雖然這個文檔很長,可是我最後發現這麼作是最節約時間的。架構

SQLAlchemy的架構

先讓咱們來看一下SQLAlchemy這個庫的整體架構,以下圖(圖來自官網)所示:

sqla_arch_small.png

SQLAlchemy這個庫分爲兩層:

  • 上面這層是ORM層,爲用戶提供ORM接口,即經過操做Python對象來實現數據庫操做的接口。

  • 下面這層是Core層,這層包含了Schema/Types, SQL Expression Language, Engine這三個部分:

    • SQL Expression Language是SQLAlchemy中實現的一套SQL表達系統,主要是實現了對SQL的DML(Data Manipulation Language)的封裝。這裏實現了對數據庫的SELECT、DELETE、UPDATE等語句的封裝。SQL Expression Language是實現ORM層的基礎。

    • Schema/Types這部分主要是實現了對SQL的DDL(Data Definition Language)的封裝。實現了Table類用來表示一個表,Column類用來表示一個列,也是實現了將數據庫的數據類型映射到Python的數據類型。上面的SQL Expression Language的操做對象就是這裏定義的Table。

    • Engine實現了對各類不一樣的數據庫客戶端的封裝和調度,是全部SQLAlchemy應用程序的入口點,要使用SQLAlchemy庫來操做一個數據庫,首先就要有一個Engine對象,後續的全部對數據庫的操做都要經過這個Engine對象來進行。下圖是官方文檔中的Engine位置的描述圖:
      sqla_engine_arch.png

      • Pool是Engine下面的一個模塊,用來管理應用程序到數據庫的鏈接。

      • Dialect是Engine下的另外一個模塊,用來對接不一樣的數據庫驅動(即DBMS客戶端),這些驅動要實現DBAPI接口。

  • 最後,SQLAlchemy還要依賴各個數據庫驅動的DBAPI接口來實現對數據庫服務的調用。DBAPI是Python定義的數據庫API的實現規範,具體見PEP0249

上面簡單的總結了SQLAlchemy的架構,但願你們可以大概瞭解一下SQLAlchemy,在後面介紹一些相關概念時,可以知道這個概念是屬於整個架構的哪一個部分。

Dialect和數據庫客戶端

上面提到了Dialect是用來對接不一樣的數據庫驅動的,它主要負責將SQLAlchemy最後生成的數據庫操做轉換成對數據庫驅動的調用,其中會處理一些不一樣數據庫和不一樣DBAPI實現的差異。這個部分通常是SQLAlchemy的開發者關心的內容,若是你只是使用SQLAlchemy來操做數據庫,那麼能夠不用關心這個部分。不過咱們仍是要來了解一下SQLAlchemy支持的和OpenStack相關的數據庫驅動。

MySQL

OpenStack項目主要是使用MySQL,以前一直都在使用MySQL-Python驅動,由於這個驅動足夠成熟和穩定。不過這個狀況正在轉變,有以下兩個緣由:

  1. MySQL-Python不支持Python3,而OpenStack正在轉換到Python3的過程當中,因此這個驅動最終是要放棄的。

  2. MySQL-Python是用C語言寫的,不支持eventlet庫的monkey-patch操做,沒法被eventlet庫轉換成異步操做,因此使用了eventlet庫的到OpenStack項目在使用MySQL數據庫時,都是進行同步的串行操做,有性能損失。

爲了解決這個問題,社區發起了一次對新驅動的評估,主要是評估PyMySQL驅動:PyMySQL Evaluation。這個評估還在社區的郵件列表發起了好幾回討論,到目前爲止的結果是:若是使用Python 2.7,那麼繼續使用MySQL-Python這個驅動,不然就使用PyMySQL這個驅動PyMySQL驅動是使用純Python寫的,不只支持Python3並且能夠支持eventlet的異步。

SQLite3

OpenStack項目通常會使用SQLite3數據庫來運行單元測試。OpenStack在Python2.7下會使用pysqlite驅動,不過這個驅動和標準庫中的sqlite3模塊是同樣的,也就是Python內置了SQLite3的驅動,你無需選擇其餘的驅動。

SQLAlchemy的基本概念和使用

使用SQLAlchemy大致上分爲三個步驟:鏈接到數據庫,定義數據模型,執行數據操做。

鏈接到數據庫

在你的應用可使用數據庫前,你要先定義好數據庫的鏈接,包括數據庫在哪裏,用什麼帳號訪問等。全部的這些工做都是經過Engine對象來進行的(記得上面提到的Engine了麼?)。

數據庫URL

SQLAlchemy使用URL的方式來指定要訪問的數據庫,整個URL的具體格式以下:

dialect+driver://username:password@host:port/database

其中,dialect就是指DBMS的名稱,通常可選的值有:postgresql, mysql, sqlite等。driver就是指驅動的名稱,若是不指定,SQLAlchemy會使用默認值。database就是指DBMS中的一個數據庫,通常是指經過CREATE DATABASE語句建立的數據庫。其餘的參數就不言而喻了。dialect和driver參數有不少選擇,具體的能夠參考官方文檔:Database URLs

建立Engine對象

肯定了要鏈接的數據庫信息後,就能夠經過create_engine函數來建立一個Engine對象了。

from sqlalchemy import create_engine

engine = create_engine('sqlite://:memory:')

create_engine函數還支持如下幾個參數:

  • connect_args:一個字典,用來自定義數據庫鏈接的參數,好比指定客戶端使用的字符編碼。

  • pool_sizemax_overflow:指定鏈接池的大小。

  • poolclass:指定鏈接池的實現

  • echo:一個布爾值,用來指定是否打印執行的SQL語句到日誌中。

還有不少其餘的參數,能夠參考官方文檔:Engine Configuration

通常來講,Engine對象會默認啓用鏈接池,會根據不一樣的dialect來選擇不一樣的默認值。通常來講,你是不用考慮鏈接池的配置的,默認狀況都配置好了。想了解關於鏈接池的更多內容,請查看官方文檔:Connection Pooling

使用Engine對象

通常來講,應用程序的代碼是不直接使用Engine對象的,而是把Engine對象交給ORM去使用,或者建立session對象來使用。不過,咱們仍是來簡單看一下Engine對象能作什麼事情。

應用程序能夠調用Engine對象的connect()方法來得到一個到數據庫的鏈接對象;而後能夠在這個鏈接對象上調用execute()來執行SQL語句,調用begin()commit()rollback()來執行事務操做;調用close()來關閉鏈接。Engine對象也有一些快捷方法來直接執行上述操做,避免了每次都要調用connect()來獲取鏈接這種繁瑣的代碼,好比engine.execute(), with engine.begin()等。

定義數據模型

有了數據庫鏈接後,咱們就能夠來定義數據模型了,也就是定義映射數據庫表的Python類。在SQLAlchemy中,這是經過Declarative的系統來完成的。

Declarative系統

根據官方文檔的描述,SQLAlchemy一開始是採用下面這種方式來定義ORM的:

  1. 首先定義一個映射類,這個類是數據庫表在代碼中的對象表示,這類的類屬性是不少Column類的實例。

  2. 而後定義一個Table對象,這裏的Table就是上面提到的在Schema/Types模塊中的一個類,用來表示一個數據庫中的表。

  3. 調用sqlalchemy.orm.mapper函數把步驟1中定義的類映射到步驟2中定義的Table。

上面這種方式稱爲Classical Mappings,看起來好麻煩啊。因此就有了Declarative系統。這個系統就是一次完成這三個步驟,你只須要定義步驟1中的類便可。這也是如今在SQLAlchemy中使用ORM的方式,無需在使用過去這種麻煩的方法。

要使用Declarative系統,你須要爲全部映射類建立一個基類,這個基類用來維護全部映射類的元信息。

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

定義映射類

如今咱們能夠開始建立映射類了。假設咱們在數據庫中有一個表Person,這個表有兩個列,分別是id和name,那麼咱們建立的映射類以下:

from sqlalchemy import Column, Integer, String

# 這裏的基類Base是上面咱們經過declarative_base函數生成的
class Person(Base):
    __tablename__ = 'person'

    id = Column(Interger, primary_key=True)
    name = Column(String(250), nullable=False)

這樣咱們就定義了一個映射類Person,後續咱們能夠經過操做這個類的實例來實現對數據庫表person的操做。在咱們的映射類中,咱們使用__tablename__屬性來指定該映射類所對應的數據庫表,經過Column類實例的方式來指定數據庫的字段。這裏,讀者可能會問:我如何能知道Column都能支持哪些類型呢?這個查看官方文檔得到:Column And Data Types

由於咱們使用了Declarative系統,因此雖然咱們本身沒有定義Table對象,可是Declarative系統幫咱們作了,而且幫咱們調用了mapper函數。所以,當咱們定義好一個表的映射類後,這個類的__table__屬性就保存了該映射類所映射的Table對象:

In [6]: Person.__table__
Out[6]: Table('person', MetaData(bind=None),
    Column('id', Integer(), table=<person>, primary_key=True, nullable=False),
    Column('name', String(length=250), table=<person>, nullable=False), schema=None)

定義映射類是咱們使用ORM的最主要的功能之一,不只能夠指定單表的映射,還可以指定表之間的關係。因爲篇幅限制,咱們在本文就不展開講了。

Schema和Metadata

關於Table對象,咱們上面也提到了,它屬於SQLAlchemy的core層的Schema/Types這個部分。SQLAlchemy中的Schema能夠理解爲和DDL相關的一套體系,它告訴SQLAlchemy的其餘部分,數據庫中的表是如何定義的。這個至關於咱們在MySQL中使用describe命令,或者在PostgreSQL中使用\d命令。

SQLAlchemy中經過schema metadata來實現上面說的Schema。Schema metadata,官方文檔中也稱爲database metadata,簡稱爲metadata,是一個容器,其中包含了和DDL相關的全部信息,包括Table, Column等對象。當SQLAlchemy要根據映射類生成SQL語句時,它會查詢metadata中的信息,根據信息來生成SQL語句。

爲了要讓metadata能夠工做,咱們須要把DDL的相關信息放到metadata中。若是你注意看上面Person.__table__的輸出,就會發現Table類的第二個參數就是一個Metadata實例,也就是說,咱們須要在定義Table的時候就把DDL信息放到metadata中。若是是是用classical mapping的方式,咱們須要先建立一個metadata實例,而後每次建立一個Table對象的時候就把metadata傳遞進去。從寫代碼的角度來講,這個方式沒有什麼問題,也不算麻煩;問題是咱們在使用ORM的過程當中,幾乎不會用到metadata,metadata基本上是給SQLAlchemy用的,對於用戶來講metadata提供的接口只能用來建立表和刪除表,這種操做的頻率遠低於查詢操做。

好在Declarative系統則幫咱們把這些都作好了。當咱們經過declarative_base()生成一個基類Base的時候,這個基類就已經包含了一個metadata實例,後面基於Base定義映射類都會被自動加入到這個metadata中。咱們能夠經過Base.metadata來訪問這個metadata實例。

說了這麼多關於metadata的內容,簡單總結一下:metadata是schema在SQLAlchemy中的實現,包含了DDL的信息,SQLAlchemy中的其餘部分須要依賴於metadata中的信息,通常用戶不多使用metadata。

不多用?那說這麼可能是作啥?主要是讓讀者能夠理解下面這個語句的原理:

Base = declarative_base()

# 基於Base定義映射類

Base.metadata.create_all(engine)

最後這行代碼是咱們最經常使用到metadata的地方:建立全部的表。咱們告訴create_all使用哪一個engine,它就會生成全部的CREATE TABLE語句,而且經過engine發送到數據庫上執行。這個在單元測試的時候頗有用。你能夠執行一下下面的代碼來觀察輸出:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine


Base = declarative_base()


class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    name = Column(String(250), nullable=False)


engine = create_engine('sqlite:///:memory:', echo=True)


Base.metadata.create_all(engine)

輸出結果以下:

...
2016-01-06 09:56:03,600 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("person")
2016-01-06 09:56:03,601 INFO sqlalchemy.engine.base.Engine ()
2016-01-06 09:56:03,602 INFO sqlalchemy.engine.base.Engine
CREATE TABLE person (
        id INTEGER NOT NULL,
        name VARCHAR(250) NOT NULL,
        PRIMARY KEY (id)
)

2016-01-06 09:56:03,603 INFO sqlalchemy.engine.base.Engine ()
2016-01-06 09:56:03,603 INFO sqlalchemy.engine.base.Engine COMMIT

關於Metadata的更多信息,請查看官方文檔:Schema Definition Language

會話

會話(session)是咱們經過SQLAlchemy來操做數據庫的入口。咱們前面有介紹過SQLAlchemy的架構,session是屬於ORM層的。Session的功能是管理咱們的程序和數據庫之間的會話,它利用Engine的鏈接管理功能來實現會話。咱們在上文有提到,咱們建立了Engine對象,可是通常不直接使用它,而是把它交給ORM去使用。其中,經過session來使用Engine就是一個經常使用的方式。

要是用session,咱們須要先經過sessionmaker函數建立一個session類,而後經過這個類的實例來使用會話,以下所示:

from sqlalchemy.orm import sessionmaker

DBSession = sessionmaker(bind=engine)
session = DBSession()

咱們經過sessionmakerbind參數把Engine對象傳遞給DBSession去管理。而後,DBSession實例化的對象session就能被咱們使用了。

CRUD

CRUD就是CREATE, READ, UPDATE, DELETE,增刪改查。這個也是SQLAlchemy中最經常使用的功能,並且都是經過上一小節中的session對象來使用的。咱們這簡單的介紹一下這四個操做,後面會給出官方文檔的位置。

Create

在數據庫中插入一條記錄,是經過session的add()方法來實現的,你須要先建立一個映射類的實例,而後調用session.add()方法,而後調用session.commit()方法提交你的事務(關於事務,咱們下面會專門講解):

new_person = Person(name='new person')
session.add(new_person)
session.commit()

Delete

刪除操做和建立操做差很少,是把一個映射類實例傳遞給session.delete()方法。

Update

更新一條記錄須要先使用查詢操做得到一條記錄對應的對象,而後修改對象的屬性,再經過session.add()方法來完成更新操做。

Read

查詢操做,通常稱爲query,在SQLAlchemy中通常是經過Query對象來完成的。咱們能夠經過session.query()方法來建立一個Query對象,而後調用Query對象的衆多方法來完成查詢操做。

事務

使用session,就會涉及到事務,咱們的應用程序也會有不少事務操做的要求。當你調用一個session的方法,致使session執行一條SQL語句時,它會自動開始一個事務,直到你下次調用session.commit()或者session.rollback(),它就會結束這個事務。你也能夠顯示的調用session.begin()來開始一個事務,而且session.begin()還能夠配合Python的with來使用。

會話, CRUD, 事務的小結

上面關於session, CRUD和事務的內容寫的比較少,由於這些功能的內容不少,並且官方文檔也寫得很全面,本文就不作一些重複說明了。咱們會在下一篇文章中經過webdemo的代碼來看看如何使用這些功能。

總結

本文介紹了OpenStack中和數據庫相關的一些知識,重點講解了SQLAlchemy這個庫的基本概念和架構。下一篇文章,咱們會經過demo來實際項目中如何使用SQLAlchemy。

相關文章
相關標籤/搜索