为什么在post_save信号中可以访问对象,但在调用另一个进程中的该信号代码时却无法访问?

16 投票
4 回答
6815 浏览
提问于 2025-04-17 08:28

大家好,我在使用Django信号时遇到了一些问题。

我有一个模型。为了让网页加载更快,我把一些需要大量处理的工作转移到了我们运行的第二个本地服务器上,这两个服务器都使用同一个数据库。我发现一个奇怪的现象:发起请求的进程可以获取到对象,但被调用的进程却获取不到。两个进程都在使用同一个数据库,分别通过80端口和[port]端口连接到Django。



在models.py中

class A(models.Model):
    stuff...

def trigger_on_post_save( sender, instance, create, raw, **keywords):
    #This line works
    A.objects.get( pk=instance.pk )
    #then we call this
    urlopen( r'http://127.0.0.1:[port]' + 
        reverse(some_view_url, args(instance_pk) ).read()

post_save.connect( trigger_on_post_save, A )

在views.py中

def some_view_function( request, a_pk ):
    #This line raises an object_not_found exception
    A.objects.get( pk=a_pk )

而且,在urlopen调用抛出异常后,数据库中并不存在这个对象。我原本以为post_save是在对象保存并写入数据库后才会被调用,这个理解是错的吗?

4 个回答

7

这里是使用装饰器的一个不错的地方。下面是对yoanis-gil回答的一个稍微扩展的版本:

from django.db import transaction
from django.db.models.signals import post_save

def on_transaction_commit(func):
    def inner(*args, **kwargs):
        transaction.on_commit(lambda: func(*args, **kwargs))
    return inner


@receiver(post_save, sender=A)
@on_transaction_commit
def trigger_on_post_save(sender, **kwargs):
    # Do things here
17

我们遇到了类似的问题,最后使用了 on_commit 回调注意:这只有在 Django 版本大于等于 1.9 时才可以使用)。所以,你可以这样做:

from django.db import transaction

class A(models.Model):
    stuff...

def trigger_on_post_save( sender, instance, create, raw, **keywords):
    def on_commit():
        urlopen(r'http://127.0.0.1:[port]' + 
                 reverse(some_view_url, args(instance_pk) ).read()
    transaction.on_commit(on_commit)

post_save.connect( trigger_on_post_save, A )

这里的意思是,你会在事务提交 之后 调用你的接口,这样参与事务的实例就已经保存下来了;)。

14

我认为post_save是在保存操作完成后触发的,但在事务提交到数据库之前。默认情况下,Django只会在请求完成后才把更改提交到数据库。

针对你的问题,有两个可能的解决方案:

  1. 手动管理你的事务,在提交后触发一个自定义信号。
  2. 让你的第二个进程稍等一会儿,等请求处理完再继续。

老实说,你的整个设置看起来有点麻烦。你可能应该考虑一下Celery,它可以帮助你处理异步任务队列。

撰写回答