flask-sqlalchemy中使用cursor遊標

songofhawk發表於2022-05-23

遊標的作用

現在經過層層封裝的web端業務系統,遊標已經不太使用了,不過在處理大規模資料的時候,還是用遊標速度快,省記憶體,可以大幅提升查詢效率。

假設某資料表有1億條記錄,都需要匯出,或者做業務資料處理,用OR Mapping一次性讀入記憶體顯然是不可能的,那麼顯然要分段處理。常見的分段方式,是通過offset+limit來指定返回的記錄視窗(flask-sqlalchemy直接提供了paginate物件實現分頁,應該是對offset+limit的封裝)。
這種方式很直觀,但缺點也很明顯:每一段的都重新執行了一遍龐大的查詢,只返回其中很小一部分資料集,對資料庫效能是巨大的浪費。

此時就應該是遊標發揮作用的時候:Cursor的價值在於,可以只執行一次查詢,快取結果位置索引,cursor記錄當前位置,之後每次通過fetch獲取n條記錄,cursor就移動n個位置,從而節省大量磁碟IO。

在實現原理上,Cursor又分為伺服器端遊標和客戶端遊標,區別在於在哪裡快取結果集索引,伺服器端快取可以讓多個連線共享結果集,而且資料庫的記憶體配置往往很高;不過呢,現在的資料庫客戶端,通常也是應用伺服器,記憶體配置也不低,做客戶端快取,有助於分擔伺服器的壓力。Sqlalchemy的預設連線,都是客戶端快取。

Sqlalchemy獲取遊標

從網上搜尋的話,通常搜出來的程式碼都是這樣的:

engine = create_engine('sqlite:///file.db')
connection = engine.connect()
try:
    cursor_obj = connection.cursor()
    cursor_obj.execute("select * from table1")
    results_one = cursor_obj.fetchall()
    cursor_obj.close()
finally:
    connection.close()

或者用raw_connection代替connection,但如果我們是在一個web環境中,使用類似flask-sqlalchemy環境,顯然不能這樣自己管理資料庫連線,而且所有的transaction也要統一管理,所以要從session中獲取連線:

    db = SQLAlchemy()
    db.init_app(app)

    session = db.session()
    cursor = session.execute('select * from user limit 10').cursor
    result = cursor.fetchall()
    print(result)

使用遊標獲取資料

得到遊標以後,就可以用遊標來獲取資料了,注意遊標只能從前往後順序移動,不可能倒回去重新獲取了,所以fetch的動作,即是獲取資料,又是移動遊標。一共有三個fetch方法:fetchone(), fetchmany(size) 和 fetchall(),顧名思義,分別為獲取一條記錄,多條記錄和全部記錄。
fetchmany和fetchall獲取的結果集為巢狀的tuple,第一層是行,第二層是列;而fetchone因為擺明了只有1條記錄,結果集就是單層tuple。例如假設user表有四個欄位
id | account | name | comment

下面這段程式碼:

        cursor = session.execute('select * from user limit 10').cursor
        result = cursor.fetchmany(2)
        print(result)
        result = cursor.fetchmany(2)
        print(result)
        result = cursor.fetchone()
        print(result)
        result = cursor.fetchall()

輸出內容類似下面的樣子:

((1, 'acc1', 'acc1', None), (2, 'acc2', 'acc2', None))
((3, 'acc3', 'acc3', None), (4, 'acc4', 'acc4', None))
(5, 'acc6', 'acc6', None)
((6, 'acc7', 'acc7', None), (7, 'acc8', 'acc8', None), (8, 'acc9', 'acc9', None), (9, 'acc10', 'acc10', None), (10, 'acc11', 'acc11', None))

相關文章