线程化Django任务不自动处理事务或数据库连接?
我在用Django设置一些定期任务,让它们在自己的线程中运行。结果发现,这些任务总是留下未完成的数据库连接进程(pgsql的“Idle In Transaction”)。
我查看了Postgres的日志,发现这些事务没有完成(没有进行回滚)。我尝试在我的函数上使用各种事务装饰器,但都没用。
于是我改为手动管理事务,自己进行回滚,这样是有效的,但进程还是显示为“Idle”。
后来我调用了connection.close(),一切都正常了。
不过我还是在想,为什么Django的常规事务和连接管理在这些从主Django线程中派生的线程任务中不起作用呢?
1 个回答
经过几周的测试和阅读Django的源代码,我终于找到了自己问题的答案:
事务
Django默认的自动提交行为在我的多线程函数中依然适用。不过,Django文档中提到:
一旦你执行了需要写入数据库的操作,Django就会生成INSERT/UPDATE/DELETE语句,然后执行COMMIT。没有隐式的ROLLBACK。
最后一句话是非常直接的。除非Django内部设置了“脏标志”,否则它不会发出ROLLBACK命令。因为我的函数只是在执行SELECT语句,所以它从未设置“脏标志”,也就没有触发COMMIT。
这与PostgreSQL认为事务需要ROLLBACK的情况相悖,因为Django发出了一个设置时区的SET命令。在查看日志时,我感到困惑,因为我不断看到这些ROLLBACK语句,并假设是Django的事务管理导致的。结果发现并不是这样,这也没关系。
连接
连接管理的部分就比较复杂了。Django实际上使用signals.request_finished.connect(close_connection)
来关闭它通常使用的数据库连接。因为在Django中,通常没有请求就不会发生任何事情,所以你会理所当然地认为这种行为是正常的。
但在我的情况下,由于这个任务是定时的,所以没有请求。没有请求就意味着没有信号。没有信号就意味着数据库连接从未关闭。
回到事务上,结果发现只要在没有任何事务管理变更的情况下调用connection.close()
,就会在PostgreSQL日志中发出我一直在寻找的ROLLBACK语句。
解决方案
解决方案是让Django的正常事务管理继续进行,然后通过以下三种方式之一来关闭连接:
- 写一个装饰器来关闭连接,并将必要的函数包装在其中。
- 利用现有的请求信号,让Django关闭连接。
- 在函数结束时手动关闭连接。
这三种方法中的任何一种都可以(并且确实可以)解决问题。