Блокировка таблиц вызывает проблемы с точкой сохранения с django - PullRequest
0 голосов
/ 02 января 2019

Я пытаюсь построить Directed-Acyclical-Graph (DAG) в базе данных MariaDB в приложении Django.Поскольку это ациклично, мне нужно убедиться, что любые добавленные элементы (вершины / ребра) не создают циклы внутри графа.

Многие клиенты будут пытаться добавлять элементы одновременно в течение дня, однако эти циклические проверки должны быть атомарными, поэтому я решил, что мне нужно использовать некоторые блокировки при добавлении / обновлении элементов.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.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 о блокировках , кажется, что транзакции фиксируются, когда вы получаете блокировку таблицы.Это нарушает мой вариант использования, так как я хочу, чтобы транзакция откатывалась, если моя проверка цикла не удалась.

1 Ответ

0 голосов
/ 03 января 2019

Любой алгоритм антициклов зависит от того, не изменяется ли таблица во время проверки.Правильный?Сколько времени занимает проверка цикла?Сколько проверок вам нужно в день?

Предполагая, что у вас есть достаточно времени, чтобы выполнить всю эту работу, подумайте:

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 может быть'правильный' способ сделать блокировку. Пожалуйста, не распространяйте это на произвольные другие ситуации.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...