代码之家  ›  专栏  ›  技术社区  ›  wakey

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

  •  0
  • wakey  · 技术社区  · 6 年前

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

    许多客户机将尝试在一天中同时添加元素,但是这些周期检查需要是原子的,所以我推断在添加/更新元素时需要使用一些锁。Django似乎没有提供这样的服务,所以我尝试使用一个raw 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 atomic 上下文中,它试图回滚到已释放的保存点。

    Executing Query 行来自 django.db.backends.mysql.base , STARTING/EXITING ATOMIC django.db.transactions.atomic __enter__ / __exit__ #### 是我在事后添加的评论,试图解释我的想法。

    ---STARTING ATOMIC  #### Atomic context wrapping my serializer's create method
    Executing query: 'SAVEPOINT `s139667621889792_x1`' - args: None
    
    ---STARTING ATOMIC  #### Atomic context wrapping my model's save method
    Executing query: 'SAVEPOINT `s139667621889792_x2`' - args: None
    
    ---INSIDE MODEL SAVE
    ---LOCKING TABLES
    Executing query: 'LOCK TABLES vertex WRITE, edge WRITE
    ---LOCKED TABLES
    
    ---STARTING ATOMIC  #### I think Django must wrap some queries in an atomic block, but this doesnt even create a savepoint
    Executing query: 'INSERT INTO `edge`...
    ---EXITING ATOMIC
    
    #### WHERE MY CYCLE CHECK CODE WOULD RUN - not implemented yet
    
    ---UNLOCKING TABLES
    Executing query: 'UNLOCK TABLES' - args: None
    ---UNLOCKED TABLES
    
    ---EXITING ATOMIC
    Executing query: 'RELEASE SAVEPOINT `s139667621889792_x2`' - args: None
    Executing query: 'ROLLBACK TO SAVEPOINT `s139667621889792_x2`' - args: None   ### WHAT I BELIEVE TO BE THE OFFENDING QUERY
    
    ---EXITING ATOMIC
    Executing query: 'ROLLBACK TO SAVEPOINT `s139667621889792_x1`' - args: None
    
    Traceback (most recent call last):
      File ".../site-packages/django/db/backends/utils.py", line 83, in _execute
        return self.cursor.execute(sql)
      File ".../site-packages/django/db/backends/mysql/base.py", line 72, in execute
        return self.cursor.execute(query, args)
      File ".../site-packages/pymysql/cursors.py", line 170, in execute
        result = self._query(query)
      File ".../site-packages/pymysql/cursors.py", line 328, in _query
        conn.query(q)
      File ".../site-packages/pymysql/connections.py", line 516, in query
        self._affected_rows = self._read_query_result(unbuffered=unbuffered)
      File ".../site-packages/pymysql/connections.py", line 727, in _read_query_result
        result.read()
      File ".../site-packages/pymysql/connections.py", line 1066, in read
        first_packet = self.connection._read_packet()
      File ".../site-packages/pymysql/connections.py", line 683, in _read_packet
        packet.check_error()
      File ".../site-packages/pymysql/protocol.py", line 220, in check_error
        err.raise_mysql_exception(self._data)
      File ".../site-packages/pymysql/err.py", line 109, in raise_mysql_exception
        raise errorclass(errno, errval)
    pymysql.err.InternalError: (1305, 'SAVEPOINT s139667621889792_x2 does not exist')
    

    如上所示,django试图回滚到它已经发布的保存点。如果删除对锁定/解锁表的调用,则此代码工作正常,但我无法再保证循环检查是原子的。

    编辑: 我读得越多,就越觉得我想要的行为是不可能的。根据 MySQL docs on locks ,当您获得表上的锁时,事务似乎已提交。这打破了我的用例,因为我希望在循环检查失败时回滚事务。

    1 回复  |  直到 6 年前
        1
  •  1
  •   Rick James diyism    6 年前

    任何反循环算法都取决于执行检查时表没有更改。对的?执行周期检查需要多长时间?你每天需要多少张支票?

    假设你有足够的时间来做所有的工作,那么考虑一下:

    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