在Django ORM中避免MySQL死锁

gur*_*let 14 python mysql django deadlock

在MySQL数据库上使用Django我收到以下错误:

OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
Run Code Online (Sandbox Code Playgroud)

故障在以下代码中出现:

start_time = 1422086855
end_time = 1422088657
self.model.objects.filter(
    user=self.user,
    timestamp__gte=start_time,
    timestamp__lte=end_time).delete()

for sample in samples:
    o = self.model(user=self.user)
    o.timestamp = sample.timestamp
    ...
    o.save()
Run Code Online (Sandbox Code Playgroud)

我有几个并行进程在同一个数据库上工作,有时它们可​​能具有相同的工作或样本数据的重叠.这就是为什么我需要清除数据库然后存储新样本,因为我不想要任何重复.

我在事务块中运行整个事情with transaction.commit_on_success()OperationalError经常得到异常.我更喜欢的是,事务不会陷入死锁,而只是锁定并等待其他进程完成其工作.

根据我的阅读,我应该正确地订购锁,但我不确定如何在Django中执行此操作.

什么是最简单的方法来确保我没有收到此错误,同时仍然确保我没有丢失任何数据?

cat*_*ran 8

使用select_for_update()方法:

samples = self.model.objects.select_for_update().filter(
                          user=self.user,
                          timestamp__gte=start_time,
                          timestamp__lte=end_time)


for sample in samples:
    # do something with a sample
    sample.save()
Run Code Online (Sandbox Code Playgroud)

请注意,您不应删除所选样本并创建新样本.只需更新过滤的记录即可.锁定这些记录将被释放,然后您的交易将被提交.

BTW而不是__gte/ __ltelookups你可以使用__range:

samples = self.model.objects.select_for_update().filter(
                          user=self.user,
                          timestamp__range=(start_time, end_time))
Run Code Online (Sandbox Code Playgroud)

  • 我刚试过`select_for_update()`(和`__range`),但我仍然看到死锁.Nb`camples`不是来自db,而是来自实际的处理工作.db仅用于存储从更大的数据集收集的一些信息. (4认同)

Luc*_*rea 6

为了避免死锁,我所做的是实现一种在发生死锁时重试查询的方法.

为了做到这一点,我做的是我修补了django的CursorWrapper类的方法"execute".无论何时进行查询,都会调用此方法,因此它将在整个ORM中运行,您不必担心项目中的死锁:

import django.db.backends.utils
from django.db import OperationalError
import time

original = django.db.backends.utils.CursorWrapper.execute

def execute_wrapper(*args, **kwargs):
    attempts = 0
    while attempts < 3:
        try:
            return original(*args, **kwargs)
        except OperationalError as e:
            code = e.args[0]
            if attempts == 2 or code != 1213:
                raise e
            attempts += 1
            time.sleep(0.2)

django.db.backends.utils.CursorWrapper.execute = execute_wrapper
Run Code Online (Sandbox Code Playgroud)

上面的代码是:它将尝试运行查询,如果抛出ErrorError并返回错误代码1213(死锁),它将等待200 ms并再次尝试.它将执行此操作3次,如果3次后问题仍未解决,则会引发原始异常.

当django项目被加载到内存中时,应该执行此代码,因此放置它的好地方是在__ini__.py任何应用程序的__ini__.py文件中(我放在项目主目录的文件中 - 具有相同名称的文件)作为你的django项目).

希望这对未来的任何人都有帮助.

  • 我担心这个钩子**只会重复导致死锁错误的最后一个数据库查询**.但在这种情况下,数据库**会恢复整个交易**.因此,如果在一个`atomic()`块内执行由多个DB查询组成的更复杂的逻辑,这将导致不需要的行为,因为在完成所有块之后可能没有在DB中记录某些语句. (5认同)