2010-10-22 707 views
4

這個例子說明了我在構建的應用程序中遇到的一個謎題。應用程序需要支持一個選項,允許用戶在不實際提交數據庫更改的情況下執行代碼。但是,當我添加此選項時,即使沒有調用commit()方法,我也發現更改仍保留在數據庫中。爲什麼此SQLAlchemy示例將更改提交給數據庫?

我的具體問題可以在代碼註釋中找到。基本目標是更清楚地瞭解SQLAlchemy何時以及爲什麼要提交給數據庫。

我的更廣泛的問題是,我的應用程序應該(a)使用全局的Session實例,還是(b)使用全局的Session類,從中實例化特定實例。基於這個例子,我開始認爲正確答案是(b)。是對的嗎? 編輯this SQLAlchemy documentation表明推薦(b)。

import sys 

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

Base = declarative_base() 

class User(Base): 
    __tablename__ = 'users' 

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

    def __init__(self, name, age = 0): 
     self.name = name 
     self.age = 0 

    def __repr__(self): 
     return "<User(name='{0}', age={1})>".format(self.name, self.age) 

engine = create_engine('sqlite://', echo = False) 
Base.metadata.create_all(engine) 

Session = sessionmaker() 
Session.configure(bind=engine) 

global_session = Session() # A global Session instance. 
commit_ages = False  # Whether to commit in modify_ages(). 
use_global  = True  # If True, modify_ages() will commit, regardless 
          # of the value of commit_ages. Why? 

def get_session(): 
    return global_session if use_global else Session() 

def add_users(names): 
    s = get_session() 
    s.add_all(User(nm) for nm in names) 
    s.commit() 

def list_users(): 
    s = get_session() 
    for u in s.query(User): print ' ', u 

def modify_ages(): 
    s = get_session() 
    n = 0 
    for u in s.query(User): 
     n += 10 
     u.age = n 
    if commit_ages: s.commit() 

add_users(('A', 'B', 'C')) 
print '\nBefore:' 
list_users() 
modify_ages() 
print '\nAfter:' 
list_users() 
+0

我試圖回答您的具體問題,但是,是的,在參考你的**編輯:**,我肯定會去的(B)的路線。全局會話實例只是要求問題與此類似。 – snapshoe 2010-10-26 05:28:41

回答

5

tl; dr - 更新是而不是實際上已提交給數據庫 - 它們是正在進行的未提交事務的一部分。


我對您對create_engine()的調用做了2個獨立的更改。 (除了這一行,我用你的代碼完全一樣發佈。)

首先是

engine = create_engine('sqlite://', echo = True) 

這提供了一些有用的信息。我不打算在這裏發表的整個輸出,但請注意沒有SQL更新命令纔會發出後list_users第二次調用()是由:

... 
After: 
xxxx-xx-xx xx:xx:xx,xxx INFO sqlalchemy.engine.base.Engine.0x...d3d0 UPDATE users SET age=? WHERE users.id = ? 
xxxx-xx-xx xx:xx:xx,xxx INFO sqlalchemy.engine.base.Engine.0x...d3d0 (10, 1) 
... 

這是一個線索,該數據是沒有堅持下去,而是繼續留在會話對象中。

我做第二個變化是對數據庫堅持到一個文件

engine = create_engine('sqlite:///db.sqlite', echo = True) 

運行腳本之前再次提供相同的輸出爲第二次調用list_users():

<User(name='A', age=10)> 
<User(name='B', age=20)> 
<User(name='C', age=30)> 

但是,如果您現在打開我們剛剛創建的數據庫並查詢它的內容,則可以看到添加的用戶被持久保存到數據庫,但年齡修改不是:

$ sqlite3 db.sqlite "select * from users" 
1|A|0 
2|B|0 
3|C|0 

所以,list_users第二次調用()從會話對象獲取其值,而不是從數據庫中,因爲在尚未提交尚未正在進行的交易。爲了證明這一點,下面的行添加到您的腳本的末尾:

s = get_session() 
s.rollback() 
print '\nAfter rollback:' 
list_users() 
+0

非常感謝。這看起來是正確的答案。不幸的是,這意味着我的示例腳本沒有證明在我的完整應用程序中觀察到的問題 - 即使我避免調用commit(),它實際上正在提交對MySQL DB的更改。時間做更多的調查! – FMc 2010-10-26 10:39:16

+0

對於create_engine調用,我會說'echo = True',而調試將是縮小發生什麼時的最佳工具之一。 – snapshoe 2010-10-27 01:08:59

0

注意,建立事務()的默認值的那個sessionmaker的相反():自動沖洗和expire_on_commit都是假的,自動提交爲True。

+0

感謝您的回覆,但我沒有按照如何解決這個問題。 – FMc 2010-10-23 00:50:43

+0

嘗試sessionmaker()而不是create_session()。 – 2010-10-23 01:47:41

+0

據我所知,我已經在使用'sessionmaker()',而不是'create_session()'。我錯過了什麼嗎? – FMc 2010-10-23 11:32:21

0

global_session在您調用modify_ages()時已經實例化並且您已經提交到數據庫。如果您在提交後重新實例化global_session,它應該啓動一個新的事務。

我的猜測是因爲你已經提交併重新使用同一個對象,每個額外的修改都會自動提交。

1

既然你說出你實際上是使用MySQL,你所看到的問題的系統上,檢查表創建時使用的發動機類型。默認值是MyISAM,它不支持ACID事務。確保你使用的InnoDB引擎,它確實做ACID事務。

可以看到哪些引擎表與

show create table users; 

使用您可以更改數據庫引擎用alter table表:

alter table users engine="InnoDB"; 
1

1.例如:只是爲了確保(或檢查)會話是否未提交更改,只需在會話對象上調用expunge_all即可。這將最有可能證明,改變竟承諾:

.... 
print '\nAfter:' 
get_session().expunge_all() 
list_users() 

2. MySQL的:正如你已經提到的,例如sqlite可能無法反映使用mysql當你真正看到的。作爲sqlalchemy - MySQL - Storage Engines記錄,有關問題的最可能的原因就是非事務性存儲引擎(如MyISAM),導致執行的autocommit模式的使用。

3屆範圍:雖然有一個全球會議聽起來像一個追求一個問題,使用新的會話爲每個小小的請求,也不是一個好主意。您應該將交易視爲交易/ unit-of-work。我找到的contextual sessions最好的兩個世界,在這裏你不必通過會話對象的方法調用的層次結構的使用情況,並在同一時間,你被賦予在多線程環境中一個非常良好的安全性。我做的,而在那裏,我知道我不想與當前運行的事務(會議)後的互動使用當地會議。

+0

謝謝。這非常有幫助。 – FMc 2010-10-28 11:21:12

相關問題