SQLAlchemy(SQLite3引擎)在多进程上下文中的相互排斥问题

时间:2014-04-20 18:14:58

标签: python-3.x concurrency sqlite sqlalchemy

我有一个SQLite3表,其中有很多行会尝试使用很多进程。每行只能由一个进程使用。为了确保互斥,我为每个进程分配了一个id,每个DB行都有一列consumed_by,表示使用它的进程。

因此,我的锁定机制包括启动事务,获取consumed_by为NULL的行,将列更新为进程的id,以及提交事务。如果两个进程尝试获取同一行,则serializable原则将确保只有其中一个进程可以提交事务。

使用纯sqlite,我设法应用了这个锁定概念,但是对于SQLAlchemy,一些行被多个进程占用。这就是我实施测试的方式:

我有以下课程:

class ToConsume(Base):
    __tablename__ = 'to_consume'
    id = Column(Integer, primary_key=True)
    consumed_by = Column(Integer,nullable=True)

我有以下函数,由100个不同的进程调用:

def consume(id):
    log = open("log/"+str(id),"w")
    while True:
        session = None
        try:
            session = Session()
            consume = session.query(ToConsume).filter_by(consumed_by=None).first()
            if consume is None:
                break
            consume.consumed_by = id
            session.commit()
            log.write(str(consume.id)+"\n")
            log.flush()
        except Exception:
            if session is not None:
                session.rollback()
    log.close()

在文件的开头,我通过以下方式初始化SQLAlchemy:

engine = sqlalchemy.create_engine('sqlite:///multithread.db', echo=False,connect_args={'timeout': 30})
def _sqlite_pragmas(dbapi_con, con_record):
    dbapi_con.execute('PRAGMA synchronous = 0;')
sqlalchemy.event.listen(engine, 'connect', _sqlite_pragmas)

Base = sqlalchemy.ext.declarative.declarative_base()
Session = sqlalchemy.orm.sessionmaker(bind=engine)

我的数据库以1000行开头。运行100个独立的python进程(我不使用线程),我预计所有日志文件的串联将导致1000行,但我得到超过3000行。

我很难理解为什么在使用SQLAlchemy时我的互斥不起作用。请注意,当我使用纯sqlite时,问题不会发生。

1 个答案:

答案 0 :(得分:2)

默认情况下,SQlite未设置为可序列化。此外,Python sqlite3驱动程序中存在错误(有关背景,请参阅http://bugs.python.org/issue9924)。要使用sqlite3 / sqlalchemy进行可序列化隔离,请参阅this document。例如:

from sqlalchemy import create_engine, event

engine = create_engine("sqlite:///myfile.db", isolation_level='SERIALIZABLE')

@event.listens_for(engine, "begin")
def do_begin(conn):
    conn.execute("BEGIN")