【问题标题】:IntegrityError: distinguish between unique constraint and not null violationsIntegrityError:区分唯一约束和非空违规
【发布时间】:2012-09-11 11:51:25
【问题描述】:

我有这个代码:

try:
    principal = cls.objects.create(
        user_id=user.id,
        email=user.email,
        path='something'
    )
except IntegrityError:
    principal = cls.objects.get(
        user_id=user.id,
        email=user.email
    )

它尝试使用给定的 id 和电子邮件创建一个用户,如果已经存在,则尝试获取现有记录。

我知道这是一个糟糕的结构,无论如何它都会被重构。但我的问题是这样的:

我如何确定发生了哪种IntegrityError:与unique 约束违规相关的一种((user_id, email) 上有唯一键)或与not null 约束相关的一种(path不能为空)?

【问题讨论】:

    标签: python django postgresql psycopg2


    【解决方案1】:

    psycopg2 提供SQLSTATE 例外,作为pgcode 成员,它为您提供了非常细粒度的错误信息以进行匹配。

    python3
    >>> import psycopg2
    >>> conn = psycopg2.connect("dbname=regress")
    >>> curs = conn.cursor()
    >>> try:
    ...     curs.execute("INVALID;")
    ... except Exception as ex:
    ...     xx = ex
    >>> xx.pgcode
    '42601'
    

    代码含义见Appendix A: Error Codes in the PostgreSQL manual。请注意,您可以粗略地匹配广泛类别的前两个字符。在这种情况下,我可以看到 SQLSTATE 42601 是 syntax_errorSyntax Error or Access Rule Violation 类别中。

    你想要的代码是:

    23505   unique_violation
    23502   not_null_violation
    

    所以你可以写:

    try:
        principal = cls.objects.create(
            user_id=user.id,
            email=user.email,
            path='something'
        )
    except IntegrityError as ex:
        if ex.pgcode == '23505':
            principal = cls.objects.get(
                user_id=user.id,
                email=user.email
            )
        else:
            raise
    

    也就是说,这是执行upsertmerge 的坏方法。 @pr0gg3d 在建议使用 Django 的正确方法方面可能是正确的;我不做 Django,所以我不能评论那一点。有关 upsert/merge 的一般信息,请参阅depesz's article on the topic

    【讨论】:

      【解决方案2】:

      2017 年 9 月 6 日更新:

      一个非常优雅的方法是try/except IntegrityError as exc,然后在exc.__cause__exc.__cause__.diag 上使用一些有用的属性(一个诊断类,它可以为您提供一些关于错误的其他超级相关信息手 - 你可以通过dir(exc.__cause__.diag)自己探索它。

      上面描述了您可以使用的第一个。为了使您的代码更具前瞻性,您可以直接引用 psycopg2 代码,甚至可以使用我上面提到的诊断类检查违反的约束:

      except IntegrityError as exc:
          from psycopg2 import errorcodes as pg_errorcodes
          assert exc.__cause__.pgcode == pg_errorcodes.UNIQUE_VIOLATION
          assert exc.__cause__.diag.constraint_name == 'tablename_colA_colB_unique_constraint'
      

      编辑澄清:我必须使用 __cause__ 访问器,因为我使用的是 Django,所以要访问 psycopg2 IntegrityError 类,我必须调用 exc.__cause__

      【讨论】:

      • 错误也可以通过exc.orig访问,但我无法区分__cause__。也许这是你澄清编辑中的地址,但我不明白,我没有看到与 Django 的关系。
      【解决方案3】:

      使用起来会更好:

      try:
          obj, created = cls.objects.get_or_create(user_id=user.id, email=user.email)
      except IntegrityError:
          ....
      

      https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

      只有在存在NOT NULL 约束违规的情况下才应引发IntegrityError。 此外,您可以使用created 标志来了解对象是否已经存在。

      【讨论】:

      • 如果有人发现这一点:get_or_create() 更简洁,但仍不总是原子的,并且会受到竞争条件(取决于数据库配置)的影响,如文档中所述。
      • IntegrityError 出现多个错误,包括 Not Null 和 Unique 约束失败。
      猜你喜欢
      • 2021-01-21
      • 2020-03-27
      • 2013-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-21
      • 1970-01-01
      • 2022-01-23
      相关资源
      最近更新 更多