锁定表会导致Django的保存点问题

时间:2019-01-02 17:12:19

标签: mysql django mariadb directed-acyclic-graphs

我正在尝试在Django应用程序的MariaDB数据库中构建有向非循环图(DAG)。因为这是非周期性的,所以我需要验证是否所有添加的元素(顶点/边)都不会在图形内创建循环。

许多客户端将全天尝试同时添加元素,但是这些周期检查必须是原子的,因此我认为在添加/更新元素时​​需要使用一些锁。 Django似乎没有提供类似的功能,因此我尝试使用原始的LOCK TABLES / UNLOCK TABLES查询。这是我用来执行此操作的代码...

def lock_tables():                                                                    
    cursor = get_connection(DEFAULT_DB_ALIAS).cursor()                            

    tables = [                                                                    
        'vertex',                                                  
        'edge'                                                                                             
    ]                                                                             

    lock_query = ', '.join(                                                
        "{} {}".format(table, 'WRITE') for table in tables                        
    )                                                                             

    query = 'LOCK TABLES {}'.format(lock_query)                            
    cursor.execute(query)                                                         


def unlock_tables():                                                                  
    cursor = get_connection(DEFAULT_DB_ALIAS).cursor()                            
    cursor.execute('UNLOCK TABLES')

然后以我的模式的save方法...

@transaction.atomic()
def save(self, *args, **kwargs):

    print("---INSIDE MODEL SAVE")

    try:
        print("---LOCKING TABLES")
        lock_tables()
        print("---LOCKED TABLES")

        super().save(*args, **kwargs)

        # TODO: Add Cycle check here
    except Exception as ex:
        print("---EXCEPTION THROWN INSIDE SAVE: {}".format(ex))
        raise
    finally:
        print("---UNLOCKING TABLES")
        unlock_tables()
        print("---UNLOCKED TABLES")   

但是,关于锁定和解锁这些表的某些事情弄乱了使用django.db.transaction.atomic创建的保存点...在某个时候,当Django尝试退出atomic上下文时,它试图回滚到保存点已经发布了。

以下是我尝试捕获问题的一些日志,Executing Query行来自django.db.backends.mysql.baseSTARTING/EXITING ATOMIC行来自django.db.transactions.atomic __enter__ / {{ 1}}方法和__exit__之后的注释是我在事实之后添加的注释,旨在解释我的想法。

####

如上所示,django尝试回滚到已经释放的保存点。如果删除对锁定/解锁表的调用,此代码将运行完美,但是我无法再保证我的循环检查是原子的。

以前有没有人遇到过这个问题,或者有任何技巧可以更深入地研究原因?

编辑:我读得越多,就越不可能实现我想要的行为。根据{{​​3}},当您锁定表时,似乎已提交事务。这破坏了我的用例,因为如果周期检查失败,我希望事务回滚。

1 个答案:

答案 0 :(得分:1)

任何防循环算法都取决于执行检查时表是否不变。正确?执行周期检查需要多长时间?您每天需要多少张支票?

假设您有足够的时间来完成所有工作,那么请考虑以下问题:

SELECT GET_LOCK('cycle_check');  -- (you may want timeout)
BEGIN;
INSERT new item in graph
perform cycle check
if ... COMMIT else ROLLBACK
SELECT RELEASE_LOCK('cycle_check');

请注意,此锁定机制与导致LOCK TABLES无效的特征不同。

要防止在循环检查期间读取数据,您还需要:

SELECT GET_LOCK('cycle_check');
SELECT ...;
SELECT RELEASE_LOCK('cycle_check');

(附带说明:GET_LOCK很少是执行锁定的“正确”方法。请不要将此方法扩展到其他任意情况。)