遊標的作用
現在經過層層封裝的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))