如何在SQLAlchemy中为PostgreSQL设置事务隔离级别?

18
我们正在使用SQLAlchemy声明性基础,并且我有一个方法,我想要隔离事务级别。简单来说,有两个进程同时写入数据库,我必须让它们在事务中执行其逻辑。默认的事务隔离级别是READ COMMITTED,但我需要能够使用SERIALIZABLE隔离级别执行一段代码。
使用SQLAlchemy如何实现这一点?现在,我基本上有一个在我们的模型中的方法,它继承自SQLAlchemy的声明性基础,需要以事务方式调用。
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
from psycopg2.extensions import ISOLATION_LEVEL_READ_COMMITTED
from psycopg2.extensions import ISOLATION_LEVEL_SERIALIZABLE

class OurClass(SQLAlchemyBaseModel):

    @classmethod
    def set_isolation_level(cls, level=ISOLATION_LEVEL_SERIALIZABLE):
        cls.get_engine().connect().connection.set_isolation_level(level)

    @classmethod
    def find_or_create(cls, **kwargs):
        try:
            return cls.query().filter_by(**kwargs).one()
        except NoResultFound:
            x = cls(**kwargs)
            x.save()
            return x

我这样做是为了使用事务隔离级别来调用它,但它没有按照我预期的那样运行。从Postgres日志中我看到隔离级别仍然是“READ COMMITTED”。能否有人帮助识别我是否做错了什么?

我正在使用SQLAlchemy 0.5.5。

class Foo(OurClass):

    def insert_this(self, kwarg1=value1):
        # I am trying to set the isolation level to SERIALIZABLE
        try:
            self.set_isolation_level()
            with Session.begin():
                self.find_or_create(kwarg1=value1)
        except Exception:  # if any exception is thrown...
            print "I caught an expection."
            print sys.exc_info()
        finally:
            # Make the isolation level back to READ COMMITTED
            self.set_isolation_level(ISOLATION_LEVEL_READ_COMMITTED)
3个回答

16

来自SQLAlchemy的维护者Michael Bayer:

请在create_engine()中使用"isolation_level"参数,并使用SQLAlchemy的最新版本,直到0.6.4发布,因为最近修复了与psycopg2相关的隔离级别错误。

您下面的方法不会影响稍后用于查询的相同连接 - 您可以使用PoolListener,在创建所有连接时设置set_isolation_level。


9
create_engine()函数的isolation_level参数似乎只影响主连接管理器,因此您会在每个连接上获得该隔离级别。您是否找到了一种与连接池兼容的方法,在每个会话/连接基础上实现这一点?根据您最初的问题,似乎您只想在某种特定的方法上使用它。 - Russ

2
事实上,以前只能在create_engine上全局指定隔离级别。最新版本的SQLAlchemy允许您在以下位置指定它: 引擎范围
eng = create_engine(
    "postgresql+psycopg2://scott:tiger@localhost/test",
    isolation_level="REPEATABLE READ",
)

个人咨询

plain_engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")

autocommit_engine = plain_engine.execution_options(isolation_level="AUTOCOMMIT")

# will normally use plain_engine
Session = sessionmaker(plain_engine)

# make a specific Session that will use the "autocommit" engine
with Session(bind=autocommit_engine) as session:
    # work with session
      ...

个人交易

sess = Session(bind=engine)

sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})

# ... work with session in SERIALIZABLE isolation level...

# commit transaction.  the connection is released
# and reverted to its previous isolation level.
sess.commit()

查看文档以获取更多详细信息。

-4

隔离级别是在事务内设置的,例如:

try:
    Session.begin()
    Session.execute('set transaction isolation level serializable')
    self.find_or_create(kwarg1=value1)
except:
    ...

来自PostgreSQL文档

如果在没有先执行START TRANSACTION或BEGIN的情况下执行SET TRANSACTION,它将似乎没有任何效果,因为事务将立即结束。


明天我会测试你的答案,如果可以的话就接受它 :) 我有预感它会成功。 - Mahmoud Abdelkader
你的回答不起作用。根据Michael Bayer的说法,事务隔离级别似乎并没有影响后来用于查询的同一连接。 - Mahmoud Abdelkader
我相信Michael Bayer的评论是关于你的代码,而不是我的。区别在于你的代码在开始事务之前设置了隔离级别。在声称代码无法工作之前,你真的尝试过它吗? - sayap
是的,它没有起作用。问题在于,当我在会话中执行它时,查询不会在同一个事务中。 - Mahmoud Abdelkader

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接