今天主要是想聊聊 N+1 problem,但因為和 ORM 有關係,雖然大家對 ORM 都很熟了,但我們還是可以來複習一下。
ORM(Object Relational Mapping) 中文WIKI 翻作物件關聯對映,但我覺得念起來很怪 XD。
講到 ORM ,後端的開發者們一定都不陌生,他帶給我們在開發上許多方便,它可以透過程式語言來執行 SQL 的指令,也可以透過程式語言裡面的物件來與資料庫交換資料 (CRUD),可以加快我們開發的速度
我們以 Python 的 SQLAlchemy 來舉例
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
# 定義 Model
class Order(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True)
product_name = Column(String)
price = Column(Float)
# 建立 PostgreSQL 連接 URL
DATABASE_URL = 'postgresql+psycopg2://username:password@localhost:5432/mydatabase'
# 建立 PostgreSQL 資料庫連接引擎
engine = create_engine(DATABASE_URL)
Base.metadata.create_all(engine)
# 建立 Session
Session = sessionmaker(bind=engine)
session = Session()
# 使用 ORM 新增一筆訂單
new_order = User(product_name="TV", price=10000)
session.add(new_order)
session.commit()
# 查詢全部訂單
orders = session.query(Order).all()
我們可以看到從建立連線到執行新增和查詢都可以用 python 的物件來完成。
它們替代的就是以下的 SQL 語法。
#新增一筆訂單
INSERT INTO orders (product_name, price) VALUES ('TV', 10000);
# 查詢訂單
SELECT * FROM orders;
在使用 ORM 時,可能常常會出現一個效能問題,也就是 **N + 1 problem **
這個問題通常來自於 ORM 的不當寫法所造成的。
orders = db.query(Order).all()
for order in orders:
product = db.query(Product).filter(Product.name == user.product_name).all()
在上面的 ORM 中,我們會先執行
這樣每次執行都會是 N+1 次查詢,在次數多了之後,就會產生效能的影響。
products = db.query(Order).join(Product).all()
options()
配合 joinedload()
或 subqueryload()
等來預先載入相關的資料,減少額外的查詢次數。舉個例子,對於 SQLAlchemy 來說,我們可以這樣寫:
orders = db.query(Order).options(joinedload(Order.product)).all()
這樣可以在查詢 Order 時自動加載相關的 Product 資料,避免在遍歷 Order 時進行額外的查詢。
N+1 problem 通常在了解發生原因後,很快就能理解怎麼解了,算是一個簡單但可能被疏忽的問題!