如何修復使用 Python ORM 工具 SQLAlchemy 時的常見陷阱
在使用 SQLAlchemy 時,那些看似很小的選擇可能對這種物件關係對映工具包的效能產生重要影響。
物件關係對映(ORM)使應用程式開發人員的工作更輕鬆,在很大程度是因為它允許你使用你可能知道的語言(例如 Python)與資料庫互動,而不是使用原始 SQL 語句查詢。SQLAlchemy 是一個 Python ORM 工具包,它提供使用 Python 訪問 SQL 資料庫的功能。它是一個成熟的 ORM 工具,增加了模型關係、強大的查詢構造正規化、簡單的序列化等優點。然而,它的易用性使得人們很容易忘記其背後發生了什麼。使用 SQLAlchemy 時做出的看似很小的選擇可能產生非常大的效能影響。
本文解釋了開發人員在使用 SQLAlchemy 時遇到的一些最重要的效能問題,以及如何解決這些問題。
只需要計數但檢索整個結果集
有時開發人員只需要一個結果計數,但是沒有使用資料庫計數功能,而是獲取了所有結果,然後使用 Python 中的 len
完成計數。
count = len(User.query.filter_by(acct_active=True).all())
相反,使用 SQLAlchemy 的 count
方法將在伺服器端執行計數,從而減少傳送到客戶端的資料。在前面的例子中呼叫 all()
也會導致模型物件的例項化,如果有很多資料,那麼時間代價可能會非常昂貴。
除非還需要做其他的事情,否則只需使用 count
方法:
count = User.query.filter_by(acct_active=True).count()
只需要幾列時檢索整個模型
在許多情況下,發出查詢時只需要幾列資料。SQLAlchemy 可以只獲取你想要的列,而不是返回整個模型例項。這不僅減少了傳送的資料量,還避免了例項化整個物件。使用列資料的元組而不是模型可以快得多。
result = User.query.all()
for user in result:
print(user.name, user.email)
反之,使用 with_entities
方法只選擇所需要的內容:
result = User.query.with_entities(User.name, User.email).all()
for (username, email) in result:
print(username, email)
每次迴圈都更新一個物件
避免使用迴圈來單獨更新集合。雖然資料庫可以非常快地執行單個更新,但應用程式和資料庫伺服器之間的往返時間將快速累加。通常,在合理的情況下爭取更少的查詢。
for user in users_to_update:
user.acct_active = True
db.session.add(user)
改用批量更新方法:
query = User.query.filter(user.id.in_([user.id for user in users_to_update]))
query.update({"acct_active": True}, synchronize_session=False)
觸發級聯刪除
ORM 允許在模型關係上進行簡單的配置,但是有一些微妙的行為可能會令人吃驚。大多數資料庫通過外來鍵和各種級聯選項維護關係完整性。SQLAlchemy 允許你使用外來鍵和級聯選項定義模型,但是 ORM 具有自己的級聯邏輯,可以取代資料庫。
考慮以下模型:
class Artist(Base):
__tablename__ = "artist"
id = Column(Integer, primary_key=True)
songs = relationship("Song", cascade="all, delete")
class Song(Base):
__tablename__ = "song"
id = Column(Integer, primary_key=True)
artist_id = Column(Integer, ForeignKey("artist.id", ondelete="CASCADE"))
刪除歌手將導致 ORM 在 song
表上發出 delete
查詢,從而防止由於外來鍵導致的刪除操作。這種行為可能會成為複雜關係和大量記錄的瓶頸。
請包含 passive_deletes
選項,以確保讓資料庫來管理關係。但是,請確保你的資料庫具有此功能。例如,SQLite 預設情況下不管理外來鍵。
songs = relationship("Song", cascade all, delete", passive_deletes=True)
當要使用貪婪載入時,應使用延遲載入
延遲載入是 SQLAlchemy 處理關係的預設方法。從上一個例子構建來看,載入一個歌手時不會同時載入他或她的歌曲。這通常是一個好主意,但是如果總是需要載入某些關係,單獨的查詢可能會造成浪費。
如果允許以延遲方式載入關係,像 Marshmallow 這樣流行的序列化框架可以觸發級聯查詢。
有幾種方法可以控制此行為。最簡單的方法是通過 relationship 函式本身。
songs = relationship("Song", lazy="joined", cascade="all, delete")
這將導致一個左連線被新增到任何歌手的查詢中,因此,songs
集合將立即可用。儘管有更多資料返回給客戶端,但往返次數可能會少得多。
SQLAlchemy 為無法採用這種綜合方法的情況提供了更細粒度的控制,可以使用 joinedload()
函式在每個查詢的基礎上切換連線的載入。
from sqlalchemy.orm import joinedload
artists = Artist.query.options(joinedload(Artist.songs))
print(artists.songs) # Does not incur a roundtrip to load
使用 ORM 進行批量記錄匯入
匯入成千上萬條記錄時,構建完整模型例項的開銷會成為主要瓶頸。想象一下,從一個檔案中載入數千首歌曲記錄,其中每首歌曲都先被轉換為字典。
for song in songs:
db.session.add(Song(`song))
相反,繞過 ORM,只使用核心的 SQLAlchemy 引數繫結功能。
batch = []
insert_stmt = Song.__table__.insert()
for song in songs:
if len(batch) > 1000:
db.session.execute(insert_stmt, batch)
batch.clear()
batch.append(song)
if batch:
db.session.execute(insert_stmt, batch)
請記住,此方法會自然而然地跳過你可能依賴的任何客戶端 ORM 邏輯,例如基於 Python 的列預設值。儘管此方法比將物件載入為完整的模型例項要快,但是你的資料庫可能具有更快的批量載入方法。例如,PostgreSQL 的 COPY
命令為載入大量記錄提供了最佳效能。
過早呼叫提交或重新整理
在很多情況下,你需要將子記錄與其父記錄相關聯,反之亦然。一種顯然的方法是重新整理會話,以便為有問題的記錄分配一個 ID。
artist = Artist(name="Bob Dylan")
song = Song(title="Mr. Tambourine Man")
db.session.add(artist)
db.session.flush()
song.artist_id = artist.id
對於每個請求,多次提交或重新整理通常是不必要的,也是不可取的。資料庫重新整理涉及強制在資料庫伺服器上進行磁碟寫入,在大多數情況下,客戶端將阻塞,直到伺服器確認已寫入資料為止。
SQLAlchemy 可以在幕後跟蹤關係和管理相關鍵。
artist = Artist(name="Bob Dylan")
song = Song(title="Mr. Tambourine Man")
artist.songs.append(song)
總結
我希望這一系列常見的陷阱可以幫助你避免這些問題,並使你的應用平穩執行。通常,在診斷效能問題時,測量是關鍵。大多數資料庫都提供效能診斷功能,可以幫助你定位問題,例如 PostgreSQL 的 pg_stat_statements
模組。
via: https://opensource.com/article/19/9/common-pitfalls-python
作者:Zach Todd 選題:lujun9972 譯者:MjSeven 校對:wxy
訂閱“Linux 中國”官方小程式來檢視
相關文章
- Python之ORM框架SQLAlchemyPythonORM框架SQL
- JavaScript 的常見“陷阱”JavaScript
- python的ORM技術:使用sqlalchemy操作mysql資料庫PythonORMMySql資料庫
- 靈活使用 SQLAlchemy 中的 ORM 查詢SQLORM
- Linux常見漏洞修復教程!Linux
- CocoaPods使用及安裝常見問題修復
- 分析資料時常見的 7 類統計陷阱
- Python中的10個常見安全漏洞及修復方法Python
- Django vs SQLAlchemy:哪個 Python ORM 更好DjangoSQLPythonORM
- 修復工具DBVERIFY的使用
- SQLAlchemy 和其他的 ORM 框架SQLORM框架
- 使用Python時常見的9個錯誤Python
- 修復 SSL Certificate Problem,如何定位及常見問題的處理策略
- 資料恢復工具Recoverit使用教程:如何修復損壞的影片資料恢復
- Flask框架之八SQLAlchemy -ormFlask框架SQLORM
- 開始資料治理時三個常見的陷阱和解決方法
- DVWA中學習PHP常見漏洞及修復方法PHP
- 運維常見軟體問題排查與修復運維
- win10系統修復工具如何啟動_win10啟動修復工具的方法Win10
- FAQ寶典之常見問題排查與修復方法
- 第三方ORM 之 SQLAlchemyORMSQL
- django | 常見 SQL 及其對應的 ORM 寫法DjangoSQLORM
- 「磁碟工具」不能修復磁碟時,怎麼做?
- sqlalchemy在python中的使用(基本使用)一SQLPython
- win10系統修復工具怎樣使用_win10系統修復工具使用步驟Win10
- Mac 上的 5 個常見錯誤程式碼以及修復辦法Mac
- Android 常見安全漏洞修復理論與實踐Android
- Python3 整合SQLAlchemy ORM 框架:實現MySQL 表初始化PythonORM框架MySql
- Python使用SQLAlchemy訪問MemFireDBPythonSQL
- Python筆記之SqlAlchemy使用Python筆記SQL
- 常見的優化工具優化
- JavaScript 中 4 種常見的記憶體洩露陷阱JavaScript記憶體洩露
- 面試Python機器學習時,常見的十個面試題Python機器學習面試題
- 分散式系統:常見陷阱和複雜性分散式
- win10系統修復引導工具如何使用【圖文教程】Win10
- directx修復工具win10怎麼用_win10系統directx修復工具使用教程Win10
- Hbase修復工具Hbck
- 磁碟修復工具TestDisk