You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django单元测试发往Postgres的查询随机活跃未完成问题排查咨询

问题排查解决方案

核心根因推测

你遇到的偶发查询挂起问题90%是单元测试场景下的事务锁等待,核心触发条件如下:

  • Django TestCase 默认仅会为 default 数据库开启事务包裹、测试结束自动回滚,你当前测试类未声明操作的非默认数据库别名,导致操作其他库的事务测试结束后未正常回滚,长期持有表/行锁
  • Jenkins/本地并发运行测试时,多个测试事务同时申请同几张表的锁,出现循环等待触发死锁
  • 单独跑SQL、生产ETL无问题的原因是这两个场景都不存在未提交的残留事务持有锁,查询可以直接拿到资源执行

排查验证步骤

  • 先在查询挂起时登录PostgreSQL执行以下命令,确认是否为锁等待:
SELECT pid, query, state, wait_event_type, wait_event 
FROM pg_stat_activity 
WHERE state = 'active' AND query LIKE '%disease_target%';

如果返回结果中wait_event_type为Lock,则可确认是锁等待导致的挂起,再执行以下语句找到阻塞会话:

SELECT 
  blocked_locks.pid AS blocked_pid, 
  blocking_locks.pid AS blocking_pid,
  a.query AS blocking_query
FROM pg_locks blocked_locks 
JOIN pg_locks blocking_locks 
  ON blocked_locks.locktype = blocking_locks.locktype 
  AND blocked_locks.database = blocking_locks.database 
  AND blocked_locks.relation = blocking_locks.relation 
  AND blocked_locks.pid != blocking_locks.pid
JOIN pg_stat_activity a ON a.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;

执行SELECT pg_terminate_backend(阻塞pid);即可临时解除挂起。

修复方案

1. 优先修复测试类的多数据库声明

在你的测试类中显式声明操作的所有数据库别名,让TestCase为所有涉及的数据库开启事务管理,测试结束自动回滚释放锁:

class NoneGroundTruthTest(TestCase):
    # 新增此行,填入你代码中self.database对应的别名,包含default
    databases = {'default', '你的自定义数据库别名'}
    
    def test_disease_target(self):
        # 原有测试逻辑不变
        ...

2. 显式设置数据库隔离级别

在settings.py的数据库配置中新增隔离级别配置,避免可重复读隔离级别下的长事务持有锁时间过长:

import psycopg2.extensions

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': os.getenv('db_name_ccm'),
        'HOST': os.getenv('db_host_ccm'),
        'PORT': os.getenv('POSTGRES_PORT'),
        'USER': os.getenv('DB_USER'),
        'PASSWORD': os.getenv('DB_SECRET_PASSWORD'),
        # 新增以下配置
        'OPTIONS': {
            'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED,
        }
    },
}

3. 并发测试优化

如果Jenkins开启了多进程并发测试,可将操作核心表的测试用例单独分组串行执行,或者调低并发数,避免多个测试同时申请锁。

4. 兜底验证方案

如果以上修改后仍有问题,可将TestCase替换为TransactionTestCase验证:

from django.test import TransactionTestCase

class NoneGroundTruthTest(TransactionTestCase):
    databases = {'default', '你的自定义数据库别名'}
    # 其余逻辑不变

TransactionTestCase不会用事务回滚来重置测试数据,而是跑完测试直接清空表,可完全排除事务残留导致的锁问题,若替换后问题消失则可100%确认根因为事务锁残留。


内容的提问来源于stack exchange,提问作者Alma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 20:12:01