SQLAlchemy session 使用問題

SQLAlchemy session 使用問題

在更改 SQLAlchemy Session 從每次請求都建立到共享同一個 Session 以後遇到了以下問題:html

StatementError: (sqlalchemy.exc.InvalidRequestError) Can’t reconnect until invalid transaction is rolled back [SQL: ]python

或者是mysql

raised unexpected: OperationalError(「(_mysql_exceptions.OperationalError) (2006, ‘MySQL server has gone away’)」,)web

錯誤是 SQLAlchemy 拋出。緣由是你從 pool 拿的 connection 沒有以 session.commit 或 session.rollback 或者 session.close 放回 pool 裏。這時 connection 的 transaction 沒有完結(rollback or commit)。 而不知什麼緣由(recyle 了,timeout 了)你的 connection 又死掉了,你的 sqlalchemy 嘗試從新鏈接。因爲 transaction 還沒完結,沒法重連。sql

正確用法是確保 session 在使用完成後用 session.close, session.commit 或者 session.rollback 把鏈接還回 pool。數據庫

SQLAlchemy 數據庫鏈接池使用

sessions 和 connections 不是相同的東西, session 使用鏈接來操做數據庫,一旦任務完成 session 會將數據庫 connection 交還給 pool。安全

在使用 create_engine 建立引擎時,若是默認不指定鏈接池設置的話,通常狀況下,SQLAlchemy 會使用一個 QueuePool 綁定在新建立的引擎上。並附上合適的鏈接池參數。服務器

在以默認的方法 create_engine 時(以下),就會建立一個帶鏈接池的引擎。session

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname')

在這種狀況下,當你使用了 session 後就算顯式地調用 session.close(),也不能把鏈接關閉。鏈接會由 QueuePool 鏈接池進行管理並複用。併發

這種特性在通常狀況下並不會有問題,不過當數據庫服務器由於一些緣由進行了重啓的話。最初保持的數據庫鏈接就失效了。隨後進行的 session.query() 等方法就會拋出異常致使程序出錯。

若是想禁用 SQLAlchemy 提供的數據庫鏈接池,只須要在調用 create_engine 是指定鏈接池爲 NullPool,SQLAlchemy 就會在執行 session.close() 後馬上斷開數據庫鏈接。固然,若是 session 對象被析構可是沒有被調用 session.close(),則數據庫鏈接不會被斷開,直到程序終止。

下面的代碼就能夠避免 SQLAlchemy 使用鏈接池:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname', poolclass=NullPool)
Session = sessionmaker(bind=engine)
session = Session()
usr_obj_list = session.query(UsrObj).all()
print usr_obj_list[0].id
session.close()

create_engine() 函數和鏈接池相關的參數有:

  • -pool_recycle, 默認爲 -1, 推薦設置爲 7200, 即若是 connection 空閒了 7200 秒,自動從新獲取,以防止 connection 被 db server 關閉。
  • -pool_size=5, 鏈接數大小,默認爲 5,正式環境該數值過小,需根據實際狀況調大
  • -max_overflow=10, 超出 pool_size 後可容許的最大鏈接數,默認爲 10, 這 10 個鏈接在使用事後,不放在 pool 中,而是被真正關閉的。
  • -pool_timeout=30, 獲取鏈接的超時閾值,默認爲 30 秒

直接只用 create_engine 時,就會建立一個帶鏈接池的引擎

engine = create_engine('postgresql://postgres@127.0.0.1/dbname')

當使用 session 後就顯示地調用 session.close(),也不能把鏈接關閉,鏈接由 QueuePool 鏈接池管理並複用。

引起問題

當數據庫重啓,最初保持的鏈接就會失敗,隨後進行 session.query() 就會失敗拋出異常 mysql 數據 ,interactive_timeout 等參數處理鏈接的空閒時間超過(配置時間),斷開

什麼時候定義 session,什麼時候提交,什麼時候關閉

基本

  • 一般來講,將 session 的生命週期和訪問操做數據庫的方法對象隔離和獨立。
  • 確保 transaction 有很是清晰的開始和結束,保持 transaction 簡短,也就意味着讓 transaction 能在一系列操做以後終止,而不是一直開放着。

    from contextlib import contextmanager

    @contextmanager def session_scope(): 「"」Provide a transactional scope around a series of operations.」」」 session = Session() try: yield session session.commit() except: session.rollback() raise finally: session.close()

是否線程安全

Session 不是爲了線程安全而設計的,所以確保只在同一個線程中使用。

若是實際上有多個線程參與同一任務,那麼您考慮在這些線程之間共享 Session 及其對象;可是在這種極不尋常的狀況下,應用程序須要確保實現正確的 locking scheme,以便不會同時訪問 Session 或其狀態。處理這種狀況的一種更常見的方法是爲每一個併發線程維護一個 Session,而是將對象從一個 Session 複製到另外一個 Session,一般使用 Session.merge() 方法將對象的狀態複製到本地的新對象中。

scoped session

想要線程安全時使用 scoped_session() ,文檔解釋

the scoped_session() function is provided which produces a thread-managed registry of Session objects. It is commonly used in web applications so that a single global variable can be used to safely represent transactional sessions with sets of objects, localized to a single thread.

using transactional=False is one solution, but a better one is to simply rollback(), commit(), or close() the Session when operations are complete - transactional mode (which is called 「autocommit=False」 in 0.5) has the advantage that a series of select operations will all share the same isolated transactional context..this can be more or less important depending on the isolation mode in effect and the kind of application.

DBAPI has no implicit 「autocommit」 mode so there is always a transaction implicitly in progress when queries are made.

This would be a fairly late answer. This is what happens: While using the session, a sqlalchemy Error is raised (anything which would also throw an error when be used as pure SQL: syntax errors, unique constraints, key collisions etc.).

You would have to find this error, wrap it into a try/except-block and perform a session.rollback().

After this you can reinstate your session.

flush 和 commit 區別

  • flush 預提交,等於提交到數據庫內存,還未寫入數據庫文件;
  • commit 就是把內存裏面的東西直接寫入,能夠提供查詢了;

reference

相關文章
相關標籤/搜索