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

如何在Django中测试PostgreSQL statement_timeout并捕获超时错误

解决PostgreSQL超时测试与错误捕获问题

先说说你当前测试失败的核心原因:你直接覆盖了_CONN_TIMEOUT和_STATEMENT_TIMEOUT变量,但Django的DATABASES配置在服务启动时就已经解析完成了,后续修改这些变量并不会自动同步到OPTIONS里的实际配置,所以断言失败,超时也没触发。下面一步步解决你的两个问题:

1. 如何测试PostgreSQL的statement_timeout和connect_timeout?

测试statement_timeout

要让超时配置生效,你需要直接修改DATABASES里的OPTIONS配置,并且关闭现有数据库连接(避免连接池复用旧配置的连接)。然后执行一个明确的慢查询(比如用PostgreSQL的pg_sleep函数)来触发超时。

修改后的测试用例如下:

from django.db import connection
from django.test import TestCase
from django.conf import settings

class DbTimeoutTest(TestCase):
    def test_db_statement_timeout(self):
        mock_s_timeout = 1
        # 保存原始配置,测试完恢复
        original_options = settings.DATABASES["default"]["OPTIONS"]["options"]
        
        try:
            # 直接修改DATABASES的statement_timeout配置
            settings.DATABASES["default"]["OPTIONS"]["options"] = f"-c statement_timeout={mock_s_timeout}ms"
            # 关闭现有连接,确保新连接使用新配置
            connection.close()
            
            # 验证配置是否更新成功
            self.assertEqual(
                settings.DATABASES["default"]["OPTIONS"]["options"],
                f"-c statement_timeout={mock_s_timeout}ms"
            )
            
            # 执行一个肯定会超时的慢查询
            with self.assertRaises(Exception) as ctx:
                with connection.cursor() as cursor:
                    cursor.execute("SELECT pg_sleep(2);")  # 睡眠2秒,远超1ms的超时设置
            
            # 验证错误是statement_timeout导致的
            self.assertIn("statement timeout", str(ctx.exception).lower())
        finally:
            # 恢复原始配置,避免影响其他测试用例
            settings.DATABASES["default"]["OPTIONS"]["options"] = original_options
            connection.close()

测试connect_timeout

连接超时的测试需要模拟一个无法快速响应的数据库地址(比如不存在的IP、未监听的端口),然后修改connect_timeout配置,尝试连接并验证超时行为:

def test_db_connect_timeout(self):
        mock_conn_timeout = 1
        # 保存原始数据库配置
        original_db_config = settings.DATABASES["default"].copy()
        original_options = settings.DATABASES["default"]["OPTIONS"].copy()
        
        try:
            # 修改为一个无法快速连接的地址(根据你的环境调整,确保会超时)
            settings.DATABASES["default"]["HOST"] = "192.168.99.99"
            settings.DATABASES["default"]["PORT"] = "5433"
            settings.DATABASES["default"]["OPTIONS"]["connect_timeout"] = mock_conn_timeout
            
            # 关闭现有连接,使用新配置创建连接
            connection.close()
            
            # 尝试连接,预期触发超时
            with self.assertRaises(Exception) as ctx:
                with connection.cursor() as cursor:
                    cursor.execute("SELECT 1;")
            
            # 验证错误是连接超时导致的
            self.assertIn("connection timeout", str(ctx.exception).lower())
        finally:
            # 恢复原始配置
            settings.DATABASES["default"] = original_db_config
            settings.DATABASES["default"]["OPTIONS"] = original_options
            connection.close()

2. 如何在代码中捕获语句超时错误?

PostgreSQL的statement超时会抛出特定的数据库异常,你可以直接捕获对应的异常类:

方式1:捕获Django包装的OperationalError

Django会将PostgreSQL的异常包装为django.db.utils.OperationalError,你可以判断错误信息来区分是否是语句超时:

from django.db.utils import OperationalError
from django.db import connection

def execute_slow_query():
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT pg_sleep(10);")  # 超过你配置的3秒超时
            return cursor.fetchone()
    except OperationalError as e:
        if "statement timeout" in str(e).lower():
            print("查询超时,已终止")
            return None
        else:
            # 其他操作错误(比如权限问题、连接失败),重新抛出
            raise

方式2:捕获psycopg的具体异常(更精准)

如果你使用的是psycopg2/psycopg3,可以直接捕获QueryCanceled异常(这是statement超时对应的具体异常类):

import psycopg2
from django.db import connection

def execute_slow_query():
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT pg_sleep(10);")
            return cursor.fetchone()
    except psycopg2.errors.QueryCanceled as e:
        if "statement timeout" in str(e).lower():
            print("语句超时被PostgreSQL终止")
            return None
    # 其他数据库错误仍需捕获
    except Exception as e:
        print(f"数据库操作出错:{str(e)}")
        raise

对于ORM操作(比如Book.objects.create()),同样可以在外层捕获这些异常,逻辑完全一致。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:16:16