如何处理SQLAlchemy的数据库表锁定策略?

1 投票
1 回答
1883 浏览
提问于 2025-04-16 11:26

有人能解释一下吗?我该如何避免应用程序在处理时卡住,比如说我有一个实体列表,并且可以跳转到详细页面。
我打开列表时,启动了一个sqlalchemy会话,然后我打开一个详细页面,再打开另一个,接着又一个,结果应用程序就卡住了,因为一个会话在阻塞另一个会话。
我不能在整个应用程序中只用一个会话,因为那样我就无法通过检查session.dirty、新建和删除属性来判断表单是否被编辑,这样应用程序的状态处理就会变得非常复杂,代码也会变得难以理解。

我需要实现其他类型的会话处理策略吗?
我需要调整sqlalchemy的映射或sql服务器吗?

这里是一个最小的工作示例:

from sqlalchemy import MetaData, Table, Column, FetchedValue, ForeignKey, create_engine
from sqlalchemy.types import BigInteger, String
from sqlalchemy.orm import mapper, relationship, sessionmaker, Session

class Ref(object):
    id = None
    name = None
    id_parent = None

class TableMapper(object):

    def __init__(self, metadata, mapped_type):
        self._table = None
        self._mapped_type = mapped_type

    def get_table(self):
        return self._table

    def set_table(self, table):
        assert isinstance(table, Table)
        self._table = table

class RefTableMapper(TableMapper):

    def __init__(self, metadata):
        TableMapper.__init__(self, metadata, Ref)
        self.set_table(Table('Ref', metadata,
                             Column('id', BigInteger,
                                    primary_key = True, nullable = False),
                             Column('name', String),
                             Column('id_parent', BigInteger,
                                    ForeignKey('Ref.id'))
                             ))
    def map_table(self):
        r_parent = relationship(Ref,
                            uselist = False,
                            remote_side = [self._table.c.id],
                            primaryjoin = (
                                self._table.c.id_parent == self._table.c.id))
        mapper(Ref, self._table,
               properties = {'parent': r_parent})
        return self._table

class Mapper(object):

    def __init__(self, url, echo = False):
        self._engine = create_engine(url, echo = echo)
        self._metadata = MetaData(self._engine)
        self._Session = sessionmaker(bind = self._engine, autoflush = False)
        ref_t = RefTableMapper(self._metadata).map_table()

    def create_session(self):
        return self._Session()

if __name__ == '__main__':
    mapp = Mapper(r'mssql://username:pwd@Server\SQLEXPRESS/DBName', True)
    s = mapp.create_session()

    rr = s.query(Ref).all()

    s1 = mapp.create_session()
    merged = s1.merge(rr)
    merged.flush()

    s2 = mapp.create_session()
    rr1 = s2.query(Ref).all() #application freezes! 

1 个回答

2

SQL Server默认的隔离模式会非常严格地锁定整个表。这意味着在进行某些操作时,整个表都会被锁住,其他操作就不能进行。比如,上面的例子可能是你在一个事务中执行了更新操作,然后在另一个事务中执行了查询操作,而第一个事务还没有完成。不过,session.merge()不接受列表,而且上面没有说明表的内容,所以很难判断具体情况。

总的来说,通常的做法是启用多版本并发控制(SQL Server称之为“行版本控制”),这样就可以合理地锁定单独的行,而不是整个表,这样可以提高效率:

ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON

ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON

关于这个的详细信息可以在这里找到:http://msdn.microsoft.com/en-us/library/ms175095.aspx

撰写回答