Mysql 锁定表会导致django出现保存点问题
我试图在Django应用程序中的MariaDB数据库中构建一个有向无环图(DAG)。因为这是非循环的,所以我需要验证任何添加的元素(顶点/边)都不会在图中创建循环 许多客户机将尝试在一天中同时添加元素,但是这些周期检查需要是原子的,因此我推断在添加/更新元素时需要使用一些锁。Django似乎没有提供类似的功能,所以我尝试使用原始的Mysql 锁定表会导致django出现保存点问题,mysql,django,mariadb,directed-acyclic-graphs,Mysql,Django,Mariadb,Directed Acyclic Graphs,我试图在Django应用程序中的MariaDB数据库中构建一个有向无环图(DAG)。因为这是非循环的,所以我需要验证任何添加的元素(顶点/边)都不会在图中创建循环 许多客户机将尝试在一天中同时添加元素,但是这些周期检查需要是原子的,因此我推断在添加/更新元素时需要使用一些锁。Django似乎没有提供类似的功能,所以我尝试使用原始的锁定表/解锁表查询。这是我用来做这件事的代码 def lock_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试图退出原子上下文时,它会尝试回滚到已发布的保存点
下面是我试图捕获问题的一些日志,执行查询
行来自django.db.backends.mysql.base
,启动/退出原子
行来自django.db.transactions.ATOMIC
输入
/退出
方法,#####
后面的注释是我在事后添加的注释,试图解释我的想法
---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尝试回滚到它已经发布的保存点。如果我删除了对锁定/解锁表的调用,这段代码可以完美地工作,但是我不能再保证我的周期检查是原子的
以前有没有人遇到过这个问题,或者有没有关于如何深入挖掘原因的建议
编辑:我读得越多,就越觉得我想要的行为是不可能的。根据,当您获得表上的锁时,事务似乎已提交。这打破了我的用例,因为我希望在循环检查失败时回滚事务 任何反循环算法都取决于在执行检查时不改变的表格。对的执行循环检查需要多长时间?你每天需要多少张支票
假设你有足够的时间完成所有的工作,那么考虑一下:
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');
请注意,此锁定机制没有导致锁定表
无效的相同特征
要防止在周期检查期间读取,还需要:
SELECT GET_LOCK('cycle_check');
SELECT ...;
SELECT RELEASE_LOCK('cycle_check');
(旁注:GET_LOCK
作为锁定的“正确”方式是极为罕见的。请不要将此扩展到任意其他情况。)我不知道锁也可以独立于表!这可能有用。。。我会回来报到的。循环检查应该阻止更改和读取(不希望有人在检查时读取新的顶点/边)@wKavey-我想您也需要获得/释放选择的内容。这是一个简单的实现,与大多数允许多个同时读卡器的db锁不同。我加了一张关于阅读的便条。(我看不到允许同时选择和避免循环检查的方法。)感谢您的回复。我正在构建一个依赖项管理系统,因此我需要对每个添加项执行此检查(以防止添加坏的依赖项),这可能会有很多开销。我可能需要重新评估我的方法,因为手动包装Django执行的选择并不是最简单的事情。