首页 > 解决方案 > 并发进程中的 Django select_for_update 函数

问题描述

当我update_or_create在多线程条件下测试功能时,我发现结果不是我想要的,他们在 MySQL 中创建了多个记录。如代码所示,update_or_create用于select .. for update锁定 MySQL 中的行,那么它在 MySQL 中应该只有一条记录。我用 SQLAlchemy 和行 sql 证明了这一点。

那么,Django 代码错了吗?

使用 Django 代码:

def get_or_create_ins():
    p, created = OxalicAcid.objects.update_or_create(defaults={"formula": "20", "degree": "80"}, name="smart")

def run():
    for i in range(10):
        t = threading.Thread(target=get_or_create_ins, args=())
        t.start()

if __name__ == "__main__":
    # more than one record will be created
    run()

使用 SQLAlchemy 代码:

@contextmanager
def transaction_atomic():
    session = Session()
    try:
        yield session
        session.commit()
    except Exception as e:
        session.rollback()
        raise e


def get_result_with_update(session, name):
    sql = text("""
        select * from acid_oxalicacid where name = :name for update 
    """)
    params = dict(name=name)
    cursor = session.execute(sql, params)
    result = cursor.fetchall()
    return result


def get_result(session, name):
    sql = text("""
        select * from acid_oxalicacid where name = :name
    """)
    params = dict(name=name)
    cursor = session.execute(sql, params)
    result = cursor.fetchall()
    return result


def create_data(session, name, degree, formula):
    sql = text("""
        insert into acid_oxalicacid (name, degree, formula) values (:name, :degree, :formula)
    """)
    params = dict(
        name=name,
        degree=degree,
        formula=formula
    )
    session.execute(sql, params)


def get_or_create():
    name = "smart"
    degree = "50"
    formula = "100"

    with transaction_atomic() as session:
        res = get_result_with_update(session, name)
        if not res:
            create_data(session, name, degree, formula)
            res = get_result(session, name)
        return res


if __name__ == "__main__":
    # Only one record be created, that's correct 
    for i in range(10):
        t = threading.Thread(target=get_or_create, args=())
        t.start()

标签: pythonmysqldjangotransactions

解决方案


因为django使用'read committed'事务隔离级别,所以会是多条记录,如果改成'repeatable read',在数据库中就只有一条记录了。


推荐阅读