【问题标题】:Missing table name in IntegrityError (Django ORM)IntegrityError 中缺少表名(Django ORM)
【发布时间】:2018-03-21 12:44:18
【问题描述】:

我在 Django 的 IntegrityError 中缺少表名:

Traceback (most recent call last):
...
    return self.cursor.execute(sql, params)
  File ".../django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File ".../django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
IntegrityError: null value in column "manager_slug" violates not-null constraint
DETAIL:  Failing row contains (17485, null, 2017-10-10 09:32:19, , 306).

有没有办法查看 INSERT/UPDATE 正在访问哪个表?

我们使用 PostgreSQL 9.6。

这是一个通用问题:如何获得更好的错误信息?

这不是关于这个特定专栏的问题。我很快就找到了相关的表格和列。但我想改进来自我们 CI 系统的错误消息。下次我想立即看到表名。

我知道,如果我在软件开发过程中看到此错误,我可以使用调试器轻松揭示缺失的信息。但在我的情况下,这发生在生产中,我只有上面的堆栈跟踪。

【问题讨论】:

  • 分享你的模型
  • @Shaonshaonty 没有必要分享我的模型。这是一个通用问题:如何获得更好的错误信息?这不是关于这个特定专栏的问题。
  • 也许我遗漏了一些东西,但堆栈跟踪不会显示您正在操作的 Django 模型吗?例如,如果模型的 save() 方法是触发异常的原因,那么它将出现在您的堆栈跟踪中。
  • 在我的情况下,我删除了迁移文件,然后再次进行了 makemigrations,一切都对我有用。
  • @TejasTank 您没有阅读/理解这一点:这是一个通用问题:如何获得更好的错误消息?这不是关于这个特定专栏的问题。

标签: python django postgresql django-orm


【解决方案1】:

我为您的问题找到的最佳解决方案是覆盖 DataBaseErrorWrapper 方法,为此转到 \django\db\utils.py 并在第 86 行替换 dj_exc_value = dj_exc_type(*exc_value.args) 为:

if exec_value.diag:
    a, b = exc_value.args + ("In the table '%s'" % exc_value.diag.table_name,)
    dj_exc_value = dj_exc_type(a + b)
else:
    dj_exc_value = dj_exc_type(*exc_value.args)

如果出现 IntegrityError,则该消息应该有效

django.db.utils.IntegrityError: null value in column "signature" violates not-null constraint
DETAIL:  Failing row contains (89, null).
In the table 'teachers_signature'

我不确定,但这应该适用于这些例外情况:

DataError
OperationalError
IntegrityError
InternalError
ProgrammingError
NotSupportedError

它对我有用,告诉我是否对你有用。请记住使用您正在使用的 Django 文件夹编辑文件

【讨论】:

  • 我认为它目前不是 MySQL 的解决方案。应该注意或者可以修复。 (请参阅我对某些后端的异常参数的比较)
  • 非常有用的属性diagDiagnostics() 类)仅受驱动程序 psycopg2 >= 2.5 支持。 Django 1.11 需要它,但 Django 1.10 也支持 2.4.5。绝对应该修复它以永远不会意外引发 AttributeError "diag" 而不是数据库错误。
  • DatabaseErrorWrapper.__exit__ 中的任何错误都非常严重,因为此方法对于在某些数据库错误后关闭不可用的连接很重要。如果没有正确关闭,这也可能会中断下一个请求。
  • @hynekcer 你是对的。有什么解决办法吗
  • +1:对于方法DatabaseErrorWrapper.__exit__ 或上下文管理器wrap_database_errors 的想法。如果将其提高到接近 Django 拉取请求的质量,这可能是最好的解决方案。 (他们建议在 Django-developers 开始讨论,因为如果有更多人反对,例如与调试工具栏和其他可处理异常 args 的工具的兼容性,则最好在问题出现之后。)我编辑了我的答案,以明确在其他后端的消息末尾没有空格。
【解决方案2】:

我倾向于使用尽可能少的依赖,并保留 3rd 方库的原样。

大多数时候,我会记录每个 sql 插入/更新/删除查询。通过这种方式,我可以毫不费力地轻松识别出哪个查询出错了。这使我可以在我的系统中跟踪谁在何时做了什么。

这恰好符合我所在行业对行动跟踪的部分监管要求。

【讨论】:

    【解决方案3】:

    此回溯中的异常消息是来自数据库驱动程序的原始消息。如果有任何东西被谷歌搜索、报告等,了解这一点和回溯是很有用的。

    所有后端的异常类是相同的 django.db.utils.IntegrityError,但消息或参数取决于后端:

    • postgresnull value in column "manager_slug" violates not-null constraint\nDETAILS...\n
    • mysql 。 . :(1048, "Column 'manager_slug' cannot be null")
    • sqlite3 。 :NOT NULL constraint failed: appname_modelname.manager_slug

    表名仅在 sqlite3 后端可见。一些后端仅使用异常的字符串参数,但mysql 使用两个参数:数字错误代码和消息。 (我喜欢接受这是一个普遍的问题,不仅是 PostgreSQL。)一些后端的作者希望应用程序的作者直接或从 SQL 中知道表名,但对于一般的 ORM 包来说并非如此。没有更好的和普遍接受的方式,如何扩展消息,即使它可以在技术上完美地完成。

    开发调试简单:

    • 在开发中的 DEBUG 模式下提供了许多附加信息(最后一帧中的“SQL”或像“myobj.save()”这样的行中的对象的类名)
    • python manage.py test --debug-sql: "打印失败时记录的 SQL 查询。"
    • 使用 sqlite3 进行开发/测试时的相同错误更易于阅读。

    ...但您可能要求在生产中出现运行时错误。

    我猜你在一个如此笼统的问题中可能的意图是什么方向对你来说可能是有趣的。

    A)traceback中最重要的信息通常是高于的几行"... /django/db/...”。对于大师来说,这非常容易。如果代码不像 Django 管理站点那样动态和通用,则很有可能使用它,其中 myobj.save() 调用附近的代码(父框架中都不包含显式模型名称)不包含显式模型名称。示例:

    # skip some initial universal code in ".../django/..."
    ...
    # our apps start to be interesting... (maybe other installed app)
    ...
    # START HERE: Open this line in the editor. If the function is universal, jump to the previous.
    File ".../me/app/...py", line 47, in my...
      my_obj.save()
    # skip many stack frames .../django/db/... below
    File ".../django/db/models/base.py", line 734, in save
      # self.save_base(...    # this line 733 is not visible
          force_update=force_update, update_fields=update_fields)
    ...
    # interesting only sql and params, but not visible in production
    File ".../django/db/backends/utils.py", line 64, in execute
      return self.cursor.execute(sql, params)
    IntegrityError (or DataError similarly)...
    

    B)通过模型的共同祖先获取信息

    class ...(models.Model):
        def save(self, *args, **wkargs):
            try:
                super(..., self).save(*args, **wkargs)
            except django.db.utils.IntegrityError as exc:
                new_message = 'table {}'.format(self._meta.db_table)
                exc.extra_info = new_message
                # this is less compatible, but it doesn't require additional reading support
                # exc.args = exc.args + (new_message,)
                reraise
    

    这可能会使多重继承的调试复杂化。

    C)在 Django db 中实现会更好,但我无法想象它会被接受并且在某些问题后不会恢复。

    【讨论】:

    • @hynecker:是的,你是对的。我更新了问题:我知道如果在软件开发过程中看到此错误,我可以使用调试器轻松揭示丢失的信息。但在我的情况下,这发生在生产中,我只有上面的堆栈跟踪。
    • @hynecker:谢谢你叫我“大师”。到目前为止,我认为“我知道我什么都不知道”:-)
    • @guettli:向 A) 添加了一个示例回溯。
    • 感谢您称我为“大师”。我仍然认为我知道的比很多专家要少得多。尽管如此,我还是写下了我的个人编程指南。欢迎反馈:github.com/guettli/programming-guidelines
    • 我将其从答案中删除。我认为您的问题最终仍然令人惊讶,即使它们看起来很简单。利他主义者的动机比竞争者更令人惊讶。
    【解决方案4】:

    我建议使用 Sentry (https://sentry.io/welcome/)。在 Sentry Issues 中,您可以观察堆栈跟踪的所有部分的所有局部变量。

    【讨论】:

    • Sentry 看起来不错。谢谢你的提示。尽管如此,在默认文本表示中包含这些重要信息(表名)会很好。 Sentry 看起来非常适合生产环境。我不确定它是否也可用于 CI 系统。
    • 您可以设置多个项目:用于开发、登台和生产。但我不确定 Sentry 是否适合每个 Ticket 都有分支的 Docker 容器。
    • “每个工单都有分支的 Docker 容器。”不知道你是怎么得出这个结论的。到目前为止,我还没有使用容器。
    • 我不是说你必须使用 Docker。我刚刚指出了一个 CI 场景,其中 Sentry 可能不是最佳解决方案。为什么您认为 Sentry 可能不适合 CI 系统?
    • 感谢您的关注。首先,我想为 prod 系统尝试哨兵。然后我会评估是否可以将其用于 CI 系统。
    【解决方案5】:

    有没有办法查看 INSERT/UPDATE 正在访问哪个表?

    如果您正在运行migrate,您只需访问标准输出并检查正在执行的迁移。然后您可以打开迁移文件并更好地了解问题。

    如果你不知道,我想你想看看PEP249 以了解可选的错误处理。由于 django 数据库包装器基于 PEP249 规范。

    DatabaseErrorWrapper中的django代码参考

    第二次编辑:

    您可以捕获完整性错误并从数据库包装器访问.messages 属性。

    伪例子:

    try:
        # operation on database
    except IntegrityError as ie:
        print(ie.wrapper.cursor.messages[:])
    

    【讨论】:

    • 不,这不在迁移中。
    【解决方案6】:

    如果你能创建sql函数你可以试试:

    创建函数以获取最后一个序列值 get_sequence_last_value (original post)

    CREATE FUNCTION public.get_sequence_last_value(name) RETURNS int4 AS '
    DECLARE
    ls_sequence ALIAS FOR $1;
    lr_record RECORD;
    li_return INT4;
    BEGIN
    FOR lr_record IN EXECUTE ''SELECT last_value FROM '' || ls_sequence LOOP
    li_return := lr_record.last_value;
    END LOOP;
    RETURN li_return;
    END;' LANGUAGE 'plpgsql' VOLATILE;
    

    在它获得序列比错误堆栈更多的表之后,并且具有列manager_slug

    SELECT table_name, column_name 
    FROM information_schema.columns 
    WHERE table_name in (
        SELECT table_name
    
        FROM (
            SELECT table_name,
                   get_sequence_last_value(
                        substr(column_default, 10, strpos(column_default, '::regclass') - 11)
                        ) as lv
            FROM information_schema.columns 
            WHERE column_default LIKE 'nextval%'
            ) as t_seq_lv
    
        WHERE lv > 17485
        )
       AND column_name = 'manager_slug';
    

    我知道解决方案不完整,但我希望它可以帮助你

    【讨论】:

    • +1 如果您可以在攻击等之后立即冻结数据库副本,但在 CI 测试之后为时已晚,这是一种极好的“取证”方法。如果我理解的话,许多 CI 服务会立即回收包括 db 在内的测试环境。
    【解决方案7】:

    您错过了“manager_slug”列的设定值。您不能在此列设置 NULL 值。您应该设置值或删除非空条件。

    IntegrityError: null value in column "manager_slug" violates not-null constraint
    

    【讨论】:

    • 请再看问题。我强调这是一个普遍的问题。
    猜你喜欢
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-02
    • 1970-01-01
    • 2012-03-17
    • 1970-01-01
    相关资源
    最近更新 更多