SQLAlchemy 的 session 是指什么
顧名思義本刽,session就是會話,對話的意思固逗,它的作用就是跟數(shù)據(jù)庫DB交互的
我們來看sqlalchemy 的session是如何創(chuàng)建的
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
# or
Session = sessionmaker()
Session.configure(bind=engine) # once engine is available
# 自定義完Session以后彬坏,我們要實例化Session挟鸠,這里創(chuàng)建了兩個session
session1 = Session()
session2 = Session()
# 之后的增刪改查就是在這個session 對象里面操作了
ed_user = User(name='ed', fullname='Ed Jones', nickname='edsnickname')
session1.add(ed_user)
The above
Session
is associated with our SQLite-enabledEngine
, but it hasn’t opened any connections yet. When it’s first used, it retrieves a connection from a pool of connections maintained by theEngine
, and holds onto it until we commit all changes and/or close the session object.
session 特點:
session的作用就是真正跟database交互的嘉赎,而engine 就是告訴session使用什么引擎去數(shù)據(jù)庫置媳,也就是mysql呢還是sqlite。
session 和連接(connection) 不等同公条,session 通過連接和數(shù)據(jù)庫進行通信拇囊。創(chuàng)建完session對象,還沒真正打開數(shù)據(jù)庫連接靶橱,當它第一次使用的時候寥袭,session就會從連接池獲取一個連接來進行跟DB交互,而這個連接池是Engine來維護的抓韩,session之后就會hold住這個連接知道commit 或者 關閉session
session 是 Query 的入口纠永,當你想要發(fā)起查詢的時候,一般用法是:session.Query(Model).filter_by(...).first()
至于線程池谒拴,就是在create_engine的時候確定的
session的autoflush的作用
- flush 的意思就是將當前 session 存在的變更發(fā)給數(shù)據(jù)庫,換句話說涉波,就是讓數(shù)據(jù)庫執(zhí)行 SQL 語句英上。
- commit 的意思是提交一個事務。一個事務里面可能有一條或者多條 SQL 語句
- SQLAlchemy 在執(zhí)行 commit 之前啤覆,肯定會執(zhí)行 flush 操作苍日;而在執(zhí)行 flush 的時候,不一定執(zhí)行 commit窗声,這個主要視 autocommit 參數(shù)而定相恃,后面會詳細講
flush的作用就是相當于在開了一個終端,然后開啟了事務start transaction , 把一堆要執(zhí)行的sql發(fā)送到終端笨觅。
假設我們有一個User表:
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
// 創(chuàng)建了一個對象拦耐,這時,這個對象幾乎沒有任何意義见剩,session 不知道它的存在
>>> user = User(name='hello')
>>>
// session.add 這個對象之后杀糯,它被 session 放到它的對象池里面去了,但這時不會發(fā)送任何 SQL 語句給數(shù)據(jù)庫苍苞,數(shù)據(jù)庫目前仍然不知道它的存在
>>> session.add(user)
>>>
// session.Query 執(zhí)行之前固翰,由于 autoflush 是 True,session 會先執(zhí)行 session1.flush(),然后再發(fā)送查詢語句
// 當 session 進行 flush 操作時骂际,session 會先建立(選)一個和數(shù)據(jù)庫的連接疗琉,然后將創(chuàng)建 user 的 SQL 語句發(fā)送給數(shù)據(jù)庫
// 所以,這個查詢是能查到 user 的
>>> user = session.query(User).filter_by(name='cosven').first()
>>> print user, user.id, user.name
>>> <__main__.User object at 0x7fce4fb663d0> 10 hello
如果 session 的 autoflush 為 False 的話歉铝,session 進行查詢之前不會把當前累計的修改發(fā)送到數(shù)據(jù)庫盈简,而直接發(fā)送查詢語句,所以下面這個查詢是查不到對象的犯戏。
# autoflush 設置成False
SessionNoAutoflush = sessionmaker(bind=engine, autoflush=False)
session3 = SessionNoAutoflush()
>>> user = User(name='hello')
>>> session3.add(user)
>>> user = session3.query(User).filter_by(name='cosven').first()
>>> print user, user.id, user.name
None
Traceback (most recent call last):
File "session.py", line 41, in <module>
print u, u.id, u.name
AttributeError: 'NoneType' object has no attribute 'id'
總結(jié)flush autoflush:
- flush 就是把sql發(fā)給mysql執(zhí)行的送火,相當于在一個終端下,start transaction, 然后把sql在事務里面執(zhí)行先匪,而此時不管最后事務是否commit种吸,insert的時候已經(jīng)占用了一個id。 緊接著查詢這條記錄呀非,也是可以查詢到的坚俗,因為實在同一個事務內(nèi),而且提交了SQL給MYSQL執(zhí)行岸裙。外部再插入數(shù)據(jù)猖败,就會跳過這個id,看上像突然跳過了一下自增id降允。這個有很大作用恩闻,有時候,一個事務內(nèi)剧董,多個操作幢尚,第二個操作依賴于前一個操作的insert_id,這時候在事務你是可以獲取到這個id的(以前一直認為需要第一個操作提交事務后才能獲取到)翅楼,然后事務回滾時尉剩,也能完整回滾。 所以說事務內(nèi)一切都是準備好的毅臊,回滾可以完整回滾
- autoflush開啟就是理茎,在同一個事務內(nèi),當前操作需要查DB記錄的管嬉,會把之前操作積累的sql自動發(fā)送給mysql執(zhí)行皂林,那么當前的查詢操作就會查到相應的數(shù)據(jù)。如果autoflush關閉了宠蚂,就不會把積累的sql發(fā)送過去式撼,當前的查詢操作也不會查詢的到。
session的autocommit作用
autocommit的意義就是是否自動提交事務求厕,commit就是用來提交事務的著隆。
這個值是關閉的扰楼,那么項目不會自動提交事務,所以默認每個請求開啟了事務美浦,并且要手動提交事務
如果這個值是開啟的弦赖,說明每個sql都是自動提交事務,也就是說不會開啟事務浦辨。mysql里面默認是開啟的蹬竖,說明開啟事務需求手動開啟,begin 或者start transaction
autocommit=False(sqlalchemy默認)就會全部請求默認開始事務流酬。
autocommit=True 這樣就不會開啟事務了币厕。所以需要手動flush修改,告訴mysql執(zhí)行什么
場景 :
flask-sqlalchemy 默認是關閉autocommit的芽腾,所以所有請求都相當于自動開啟了事務旦装,哪怕是查詢的sql也開了。就是因為這個摊滔,項目中出了問題阴绢。我的api項目,基本都是查詢艰躺,沒有寫入的請求呻袭。然而這個項目請求量很大,我有幾次項目需要修改表結(jié)構(gòu)alert增加字段腺兴,結(jié)果一直卡住了左电,導致服務也請求不了,直接掛了页响。
后天排查了泳猬,發(fā)現(xiàn)是因為我的API項目刹孔,每個請求都開了事務翎迁,而且不會主動去commit 或者rollback胆建,那么請求結(jié)束后绽族,事務依然存在連接中卵酪,這時候去alert表烹骨,這個操作需要獲取鎖莫绣,但是由于有事務存在烟瞧,它獲取不到鎖诗鸭,導致在waiting鎖,而外面的請求也需要鎖來開啟事務参滴,這樣就有了死鎖效應了强岸。兩邊都釋放不了。
根本原因就是因為砾赔,就連查詢的請求SQL都開了事務蝌箍,這是根本沒必要的青灼。后面我把項目的session的autocommit改成了True, 就不會開啟事務了妓盲。
而在沒有開事務的情況下杂拨,需要寫入或者修改記錄,則需要調(diào)用session.flush()來生效
場景還原:
session1 開啟事務悯衬,并且查詢team表弹沽,這時的查詢只是快照查,不是當前讀筋粗,理論上不會產(chǎn)生鎖的
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from team;
+------+------+------+----+----+----+----+----+----+----+----+----+----+------+
| h_id | g_id | num | c2 | c3 | c5 | c6 | c7 | c8 | c9 | d1 | d2 | d3 | d4 |
+------+------+------+----+----+----+----+----+----+----+----+----+----+------+
| 1 | 2 | 40 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | NULL |
| 2 | 4 | 37 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | NULL |
| 3 | 4 | 40 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | NULL |
+------+------+------+----+----+----+----+----+----+----+----+----+----+------+
3 rows in set (0.00 sec)
session2 , 修改表結(jié)構(gòu)策橘,增加字段,這時候已經(jīng)被卡住了娜亿,
mysql> alter table team add d5 int;
session3 再開一個終端丽已,session3模擬其他用戶的請求, 這時再次select * team 快照讀也卡住了。之后的請求就是這么卡住的暇唾,導致最后的服務崩潰
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from team where id =2
-> ;
查看processlist;
mysql> mysql> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+-----------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+---------------------------------+-----------------------------+
| 18 | root | localhost | test | Query | 257 | Waiting for table metadata lock | alter table team add d5 int |
| 19 | root | localhost | test | Sleep | 263 | | NULL |
| 20 | root | localhost | test | Query | 0 | starting | show processlist |
+----+------+-----------+------+---------+------+---------------------------------+-----------------------------+
3 rows in set (0.00 sec)
id=18的線程促脉,也就是alter表那個線程,在等待鎖策州,此時已經(jīng)鎖住了team表瘸味,其他的線程也不能獲取鎖讀取team表。
總結(jié) 所以不能隨便給項目開啟autocommit够挂,因為很多情況下旁仿,讀請求根本不需要事務。