在10个并发uwsgi工作者中锁定Django数据库 - 如何实现?

3
什么是在进行复杂事务(读取、决策、写入)期间锁定Django数据库的最优雅方式,期间没有其他uwsgi工作者可以访问该表(或至少没有写入访问)?
我正在使用Django + db.sqlite3 + uwsgi (+ nginx)。
非常感谢!

编辑1:

太棒了,当然Django本身解决了这个问题。比我预想的要容易得多。很高兴我问了一下周围的人!

这是我用黄色记号笔标出来的手册页面http://marker.to/W0CbtZ关于事务的版本https://docs.djangoproject.com/en/1.7/topics/db/transactions/

谢谢,IRC :-)


编辑2:

实际上我正在寻找一个数据库锁,使得所有进程都等待直到轮到它们。

今天我学到了什么,现在我已经实现了:

try: 
   with transaction.atomic():
      foo(obj)
      obj.save()     # (*)
except IntegrityError:
   print "debug information"

所以我永远不会遇到不一致的数据库。但现在,(*)会抛出异常“OperationalError: database is locked”(而this suggestion也没有帮助)。这是可以理解的,因为许多此类事务正在同时尝试保存到数据库中。我需要的是像...这样的东西。
lock = threading.Lock()
...
with lock:
    foo(obj)
    obj.save()

但不包括线程,因为它需要锁定所有uwsgi工作进程。

欢迎任何想法。最优雅的方法是什么?现在应该查看手册的哪个部分?我已经搜索过谷歌,并没有找到答案 - 这就是我在这里提问的原因。

非常感谢。


编辑 3:

尽管我使用了 我的线程锁,但仍然有一小部分“OperationalError:database is locked”错误。因此,我实施了 @knbk 的建议,但它没有起作用,以下是输出内容:

up to here all is fine
ALERT! cannot start a transaction within a transaction
Out[]: False

这是我按照 @knbk 的 代码示例 实现的方式:

from django.db import connection

def foobar():
    cursor = connection.cursor()
    cursor.execute('BEGIN IMMEDIATE')  # also tried 'BEGIN EXCLUSIVE'
    try:
        myObjects = myModel.objects.filter(myFilter="myfilter")
        if myCondition(myObjects) > 0:
            obj = myObjects[0]
            print "up to here all is fine"
            obj.save()                                          
    except Exception as e:
        connection.rollback()
        print "ALERT!", e
        return False
    finally:
        connection.commit()
    return True

那么...现在怎么办?


编辑4:

我现在已经自己解决了。我编写了一个完整的lockbydir.DLock类,它使用目录存在和年龄-用于跨进程锁定!现在我可以简单地锁定对Django DB的访问,而不管使用哪个DB。非常高兴。也许你想看一下?这是GIT链接:

https://github.com/drandreaskrueger/lockbydir

请查看README文件。甚至有3个GIT播放器可以在浏览器中实时观看代码示例的执行!很棒,不是吗?;-)

感谢您的时间和关注!


在IRC上得到了这个提示,现在开始阅读它:https://docs.djangoproject.com/en/1.7/topics/db/transactions/ - akrueger
现在(使用postgresql),我再次在我的数据库中获得更多的重复条目,这是由于并发进程导致的 - 所以我仍然缺少一个数据库锁。任何帮助都将不胜感激!谢谢。 - akrueger
2个回答

2

获得锁的正确语句是BEGIN IMMEDIATEBEGIN EXCLUSIVE。第一个将获取一个锁,阻止其他进程获取写锁。第二个将获取一个锁,也会阻止其他进程获取读锁。

Django没有提供执行这些语句的高级API。相反,您需要使用数据库游标直接执行这些语句:

from django.db import connection

cursor = connection.cursor()
cursor.execute('BEGIN IMMEDIATE')
try:
    my_objs = Model.objects.filter(...)
    etc...
except:
    connection.rollback()
finally:
    connection.commit()

免责声明:请注意,这种方法,特别是COMMIT EXCLUSIVE,将在整个数据库上获取锁。 Sqlite不支持行或表级锁定。在锁定期间,没有其他进程能够写入数据库的任何部分,并且使用独占锁定时,它们甚至无法读取。如果这是频繁的操作或您的数据库具有较强的写入性能,请考虑使用支持行/表级锁定的MySQL或PostgreSQL。如果您的站点需要10个并发工作程序,则这可能是一个好主意。


下一个对于被锁定的数据库/表/行的读取请求会等待吗?例如,在另一个请求中进行标准的ObjectModel.objects.all()调用? - warath-coder
1
@warath-coder 是的,直到数据库超时。默认情况下,在引发错误之前等待5秒钟。@akrueger 我测试过了,你不需要关闭自动提交。此外,您可以使用connection.commit()connection.rollback()方法来提交/回滚事务。我已经相应地更新了我的答案。 - knbk
非常感谢。我已经尝试了。请查看我上面的第三次编辑。 - akrueger
@akrueger 你需要锁定的实际操作是什么?如果它非常耗时(即需要超过5秒),其他连接将会超时。您可以增加超时时间或更改为允许更多并发的数据库。请注意,您的threading.Lock只能防止同一进程中的其他线程执行完全相同的操作,而不能防止执行从/向数据库读取/写入的其他操作。 - knbk
我实际上发现了许多关于类似问题的错误报告;这似乎是sqlite一个众所周知(但不易复制)的问题,而且已经持续了很多年。希望情况确实如此,当我摆脱sqlite时,这个问题就会消失。现在终于开始研究postgresql了。 - akrueger
显示剩余7条评论

-2

目前,我通过添加一个 threading.Lock() 几乎解决了所有问题!很明显,大部分问题都是由于同一工作进程的并发线程尝试访问不适用于此的数据库(sqlite3)而引起的。

现在,成功的解决方案看起来像这样:

from django.db import transaction, IntegrityError, OperationalError

import threading
lock = threading.Lock()

def foobar():
    with lock:  
        try:
            with transaction.atomic():
                obj = readFromDB() 
                if foo(obj): 
                    obj.save()                                          
        except (IntegrityError, OperationalError) as e:
            print "ALERT!", e
            return ABORTED
    return CORRECTO

几乎总是返回CORRECTO,即使使用数十个并发请求攻击服务器,每秒造成数百个数据库访问。

然而,很少见到“OperationalError:database is locked”。因此,这些剩余的情况现在明确由进程(而不是线程)并发问题引起。我的Django DB锁在哪里,我非常希望它存在? :-)

非常感谢所有试图帮助我的人!


我解决了。请查看我原始问题中的“EDIT 4”:Lock Django DB on 10 concurrent uwsgi workers - how to?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接