SQLAlchemy 基礎知識 - autoflush 和 autocommit(轉)

奋斗终生發表於2024-03-28

原文:https://zhuanlan.zhihu.com/p/48994990

作者:Cosven

來源:知乎

這篇文章致力於解決以下疑問(本文以 MySQL 為例):

  1. SQLAlchemy 的 session 是指什麼?
  2. session 的 autoflush 引數是幹什麼的,我到底要不要開啟它?
  3. session 的 autocommit 引數又是什麼,它和 autoflush 的區別是什麼?
  4. SQLAlchemy 是在何時傳送 SQL 語句的?

附:

  1. SQLAlchemy MySQL 除錯小技巧

SQLAlchemy 基礎

下面是一段官方 SQLAlchemy 使用示例,我們從這個例子出發,認識 SQLAlchemy。

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


# sqlite3/mysql/postgres engine
# 請先自己在 MySQL 中建立一個名為 test_tmp 的 database
engine = create_engine('mysql://root@localhost/test_tmp', echo=False)

Base = declarative_base()

Session = sessionmaker(bind=engine)

session1 = Session()
session2 = Session()

SessionNoAutoflush = sessionmaker(bind=engine, autoflush=False)
session3 = SessionNoAutoflush()


class User(Base):
    __tablename__  = 'user'

    id = Column(Integer, primary_key=True)
    name = Column(String(64))

session 是什麼?

目前還不知道怎樣直接給 session 下定義,但是我們可以透過它的一些用途來認識它, 在腦海裡腦補出這個東西。

  1. session 會在需要的時候(比如使用者讀取資料、更新資料時)和資料庫進行通訊,獲取資料物件,並有一個池子來維護這些物件,保證你訪問資料時不出現意外的問題
  2. session 和連線(connection) 不等同,session 透過連線和資料庫進行通訊
  3. session 是 Query 的入口,當你想要發起查詢的時候,一般用法是:session.Query(Model).filter_by(...).first()

如果不完全理解它,也沒關係,有個大概印象即可,以後碰到具體的問題再具體分析, 到時候就可以針對性解決。

官方介紹 session 的資料:https://docs.sqlalchemy.org/en/20/orm/session_basics.html#what-does-the-session-do

autoflush 引數

首先,學習兩個概念:flush 和 commit。

  • flush 的意思就是將當前 session 存在的變更發給資料庫,換句話說,就是讓資料庫執行 SQL 語句。
  • commit 的意思是提交一個事務。一個事務裡面可能有一條或者多條 SQL 語句
  • SQLAlchemy 在執行 commit 之前,肯定會執行 flush 操作;而在執行 flush 的時候,不一定執行 commit,這個主要視 autocommit 引數而定,後面會詳細講

當 autoflush 為 True 時(預設是 True),session 進行查詢之前會自動把當前累計的修改傳送到資料庫(注意:autoflush 並不是說在 session.add 之後會自動 flush),舉個例子(結合開始的程式碼):

# 建立了一個物件,這時,這個物件幾乎沒有任何意義,session 不知道它的存在
>>> user = User(name='cosven')
>>> 
# session1.add 這個物件之後,它被 session 放到它的物件池裡面去了,但這時不會傳送任何 SQL 語句給資料庫,資料庫目前仍然不知道它的存在
>>>  session1.add(user)
>>>  
# session1.Query 執行之前,由於 autoflush 是 True,session1 會先執行 session1.flush(),然後再傳送查詢語句
# 當 session 進行 flush 操作時,session 會先建立(選)一個和資料庫的連線,然後將建立 user 的 SQL 語句傳送給資料庫
# 所以,這個查詢是能查到 user 的
>>> session1.query(User).filter_by(name='cosven').first()
<__main__.User object at 0x1108f04e0>

如果 session 的 autoflush 為 False 的話,session 進行查詢之前不會把當前累計的修改傳送到資料庫,而直接傳送查詢語句,所以下面這個查詢是查不到物件的。

>>> session3.add(User(name='haha'))
>>> session3.query(User).filter_by(name='haha').first()  # None

再重複的總結一下:

session.flush 的意義:session 計算自己積累的變更,將變更對應的 SQL 語句傳送給資料庫。 autoflush 的意義:session 在進行查詢之前,自動的進行一次 flush 操作。

autocommit 引數

commit 對應的概念是事務(transaction),預設情況下,session 引數 autocommit 的值是 False,SQLAlchemy 也推薦將它設定為 False。

注:MySQL client 預設是將 autocommit 設為 True 的,所以我們在 cli 中執行一條 SQL 語句,資料庫的資料就會發生變化

這裡複習一下一個基礎知識點:在一個事務被提交之前,事務裡面的修改只對當前事務可見,其它事務看不見。什麼意思?我們看個例子

# ps: session1 的 autocommit 引數為 False, autoflush 引數為 True
# 當 session1 執行 add 操作時,
>>> session1.add(User(name='miao'))

# session1 中是可以查到這個 user 的
>>> session1.query(User).filter_by(name='miao').first()
<__main__.User object at 0x1108f00000>

# session3 中查不到
>>> session3.query(User).filter_by(name='miao').first()  # None

# 讓 session1 提交一下當前的事務
>>> session1.commit()

# 再從 session3 中查
>>> session3.query(User).filter_by(name='miao').first() is not None
True

事務不僅可以提交,還可以 rollback,這裡就不講。

SQLAlchemy MySQL 除錯小技巧

為 MySQL 開啟查詢 log

SET GLOBAL log_output = "FILE"; the default.
SET GLOBAL general_log_file = "/path/to/your/mysql.log";
SET GLOBAL general_log = 'ON';

然後在 shell 中 tail -f mysql.log,這樣一來,當 MySQL 收到請求時,你就能看到一條日誌, 這樣可以方便你判斷 session 執行什麼操作時,會傳送 SQL 語句,什麼時候建立連線。

日誌示例:

2018-11-08T15:12:41.332513Z    53 Query commit
2018-11-08T15:12:41.333753Z    53 Query rollback
2018-11-08T15:12:45.999996Z    43 Query select * from user

將上面的指令碼匯入 python 或者 ipython

python -i test.py