数据库

Django 官方支持以下数据库:

还有一些第三方提供的 数据库后端

Django 试图在所有数据库后端上支持尽可能多的功能。然而,并不是所有的数据库后端都是一样的,我们不得不在设计上决定支持哪些功能,以及我们可以安全地做出哪些假设。

该文件描述了一些可能与 Django 使用有关的功能。 它不能替代特定于服务器的文档或参考手册。

通用注意事项

持久连接

持久连接避免了在每次请求中重新建立与数据库连接的开销。它们由 CONN_MAX_AGE 参数控制,该参数定义了一个连接的最大寿命。它可以为每个数据库独立设置。

默认值是 0,保留了每次请求结束时关闭数据库连接的历史行为。要启用持久连接,可将 CONN_MAX_AGE 设置为正整数秒。对于无限制的持久连接,将其设置为 None

连接管理

当 Django 第一次进行数据库查询时,就会打开一个与数据库的连接。它保持这个连接的开放性,并在以后的请求中重复使用。一旦连接超过了 CONN_MAX_AGE 所定义的最大时长,或者当它不再可用时,Django 就会关闭这个连接。

详细来说,Django 每当需要连接数据库而又没有连接的时候,就会自动打开一个连接到数据库——或者是因为这是第一个连接,或者是因为之前的连接被关闭。

在每次请求开始时,如果连接已达到最大时长,Django 就会关闭连接。如果你的数据库在一段时间后终止了空闲的连接,你应该将 CONN_MAX_AGE 设置为一个较低的值,这样 Django 就不会尝试使用一个已经被数据库服务器终止的连接。(这个问题可能只影响到流量很低的网站。)

在每个请求结束时,如果连接已经达到最大时长,或者连接处于不可恢复的错误状态,Django 就会关闭连接。如果在处理请求的过程中发生了任何数据库错误,Django 会检查连接是否还能使用,如果不能使用,则关闭连接。因此,数据库错误最多影响一个请求;如果连接变得不可用,下一个请求就会得到一个新的连接。

附加说明

由于每个线程都维护自己的连接,所以你的数据库必须支持至少与你的工作线程一样多的同时连接。

有时,数据库不会被大多数视图访问,例如因为它是外部系统的数据库,或者由于缓存的原因。在这种情况下,你应该将 CONN_MAX_AGE 设置为一个低值,甚至 0,因为维护一个不太可能被重复使用的连接是没有意义的。这将有助于保持这个数据库的同时连接数较少。

开发服务器每处理一个请求都会创建一个新的线程,消除了持久连接的影响。在开发过程中不要启用它们。

当 Django 建立与数据库的连接时,它会根据所使用的后端设置相应的参数。如果你启用了持久化连接,就不再每次请求都重复这种设置。如果你修改了连接的隔离级别或时区等参数,你应该在每次请求结束时恢复 Django 的默认值,在每次请求开始时强制设置一个合适的值,或者禁用持久连接。

编码

Django 假设所有的数据库都使用 UTF-8 编码。使用其他编码可能会导致意外的行为,比如数据库中的数据在 Django 中是有效的,却出现“value too long”的错误。关于如何正确设置数据库,请参考下面的数据库具体说明。

PostgreSQL 注意事项

Django 支持 PostgreSQL 9.6 及以上版本。psycopg2 2.5.4 或更高版本是必需的,不过建议使用最新版本。

PostgreSQL 连接配置

详见 HOST

优化 PostgreSQL 的配置

Django 的数据库连接需要以下参数:

  • client_encoding'UTF8'
  • default_transaction_isolation'read committed' 默认情况下,或在连接选项中设置的值(见下文)。
  • timezone

如果这些参数已经有了正确的值,Django 就不会为每个新的连接设置这些参数,这样可以稍微提高性能。你可以直接在 postgresql.conf 中设置它们,或者更方便地在每个数据库用户中用 ALTER ROLE 设置。

如果没有这个优化,Django 也能正常工作,但每个新的连接都会做一些额外的查询来设置这些参数。

隔离等级

和 PostgreSQL 本身一样,Django 默认为 READ COMMITTED 隔离级别 。如果你需要更高的隔离级别,比如 REPEATABLE READ 或者 SERIALIZABLE,可以在数据库配置的 DATABASES 中的 OPTIONS 部分进行设置:

import psycopg2.extensions

DATABASES = {
    # ...
    'OPTIONS': {
        'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE,
    },
}

注解

在更高的隔离级别下,你的应用程序应该准备好处理序列化失败时引发的异常。这个选项是为进阶用途设计的。

varchartext 列的索引。

当指定 db_index=True 时,Django 通常会输出一条 CREATE INDEX 语句。 但是,如果字段的数据库类型是 varchartext (例如,CharFieldFileFieldTextField),那么 Django 将为该列创建一个额外的索引,使用适当的 PostgreSQL 操作类 。 这个额外的索引对于正确执行在 SQL 中使用 LIKE 操作符的查询是必要的,就像 containsstartswith 查询类型一样。

添加扩展的迁移操作

如果你需要使用迁移来添加 PostgreSQL 扩展(如 hstorepostgis 等),请使用 CreateExtension 操作。

服务器端游标

当使用 QuerySet.iterator() 时,Django 会打开一个 服务器端游标。默认情况下,PostgreSQL 假设只有前 10% 的游标查询结果会被获取。查询规划器花费更少的时间规划查询,并更快地开始返回结果,但如果超过 10% 的结果被检索,这可能会降低性能。PostgreSQL 对一个游标查询的检索行数的假设是通过 cursor_tuple_fraction 选项控制的。

事务池和服务器端游标

在事务池模式下使用连接池(如 PgBouncer )需要禁用该连接的服务器端游标。

服务器端游标是本地连接,当 AUTOCOMMITTrue 时,服务器端游标在事务结束时保持开放。后续事务可能会尝试从服务器端游标中获取更多的结果。在事务池模式下,不能保证后续事务会使用同一个连接。如果使用了不同的连接,当事务引用服务器端游标时就会出现错误,因为服务器端游标只有在创建它们的连接中才能访问。

一种解决方案是在 DATABASES 中通过将 DISABLE_SERVER_SIDE_CURSORS 设置为 True 来禁用连接的服务器端游标。

为了从事务池模式下的服务器端游标中获益,你可以设置 与数据库的另一个连接,以便执行使用服务器端游标的查询。这个连接需要直接连接到数据库或者连接到会话池模式下的连接池。

另一种选择是将每个使用服务器端游标的 QuerySet 包裹在一个 atomic() 块中,因为它在事务的持续时间内禁用 autocommit。这样一来,服务器端游标将只在事务持续时间内有效。

手动指定自增主键的值。

Django 使用 PostgreSQL 的 SERIAL 数据类型 来存储自动递增的主键。SERIAL 列是用 sequence 的值来填充的,它跟踪下一个可用的值。手动给自增的字段赋值,并不会更新字段的序列,这可能会在以后引起冲突。例如:

>>> from django.contrib.auth.models import User
>>> User.objects.create(username='alice', pk=1)
<User: alice>
>>> # The sequence hasn't been updated; its next value is 1.
>>> User.objects.create(username='bob')
...
IntegrityError: duplicate key value violates unique constraint
"auth_user_pkey" DETAIL:  Key (id)=(1) already exists.

如果需要指定这样的值,请在之后重置序列,以避免重复使用表中已有的值。sqlsequencereset 管理命令会生成这样的 SQL 语句。

测试数据库模板

你可以使用 TEST['TEMPLATE'] 配置来指定一个 template (例如 'template0')来创建测试数据库。

使用非持久设置加快测试执行速度。

你可以通过 将 PostgreSQL 配置为非持久 来加快测试执行时间。

警告

这是很危险的:它将使你的数据库在服务器崩溃或断电的情况下更容易发生数据丢失或损坏。只有在开发机器上使用,在那里你可以轻松的恢复集群中所有数据库的全部内容。

MariaDB 注意事项

Django 支持 MariaDB 10.2 及以上版本。

要使用 MariaDB,请使用 MySQL 后端,两者共享。详情请看 MySQL 注意事项

MySQL 注意事项

版本支持

Django 支持 MySQL 5.7 及以上版本。

Django 的 inspectdb 功能使用 information_schema 数据库,其中包含所有数据库架构的详细数据。

Django 希望数据库支持 Unicode(UTF-8 编码),并将执行事务和引用完整性的任务交给它。需要注意的是,MySQL 在使用 MyISAM 存储引擎时,后两项其实并没有强制执行,参见下一节。

存储引擎

MySQL 有几个 存储引擎 。你可以在服务器配置中更改默认的存储引擎。

MySQL 的默认存储引擎是 InnoDB 。这个引擎是完全事务性的,并且支持外键引用。这是推荐的选择。然而,InnoDB 自动增量计数器在 MySQL 重启时丢失,因为它不记得 AUTO_INCREMENT 值,而是将其重新创建为 "max(id)+1"。这可能会导致无意中重用 AutoField 值。

MyISAM 的主要缺点是不支持事务,也不执行外键约束。

MySQL 数据库 API 驱动程序

MySQL 有几个驱动程序实现了 PEP 249 中描述的 Python 数据库 API。

  • mysqlclient 是一个原生驱动。它是 推荐的选择
  • MySQL Connector/Python 是一个来自 Oracle 的纯 Python 驱动,不需要 MySQL 客户端库或标准库之外的任何 Python 模块。

这些驱动程序都是线程安全的,并提供连接池。

除了数据库 API 驱动之外,Django 还需要一个适配器来从其 ORM 中访问数据库驱动。Django 为 mysqlclient 提供了一个适配器,而 MySQL Connector/Python 则包含了 自己的

mysqlclient

Django 需要 mysqlclient 1.4.0 或更高版本。

MySQL Connector/Python

MySQL Connector/Python 可从 下载页面 。Django 适配器在 1.1.X 及以后的版本中可用。它可能不支持最新版本的 Django。

时区定义

如果你打算使用 Django 的 时区支持,使用 mysql_tzinfo_to_sql 将时区表加载到 MySQL 数据库中。这只需要为你的 MySQL 服务器做一次,而不是每个数据库。

创建你的数据库

你可以使用命令行工具和这个 SQL 来 创建你的数据库

CREATE DATABASE <dbname> CHARACTER SET utf8;

这确保了所有的表和列默认使用 UTF-8。

字符序配置

一列的字符序配置控制了数据排序的顺序,以及哪些字符串被比较为相等。你可以指定 db_collation 参数来为 CharFieldTextField 设置列的字符序名称。

字符序也可以在整个数据库层面和每张表上设置。这在 MySQL 文档中有详细的记录。在这种情况下,你必须通过直接操作数据库配置或表来设置字符序。Django 并没有提供一个 API 来改变它们。

默认情况下,对于 UTF-8 数据库,MySQL 将使用 utf8_general_ci 字符序。这将导致所有字符串的平等比较以一种 不区分大小写 的方式进行。也就是说,"Fred""freD" 在数据库级别被认为是相等的。如果你在一个字段上有一个唯一的约束,那么试图将 "aa""AA" 插入到同一列中是不合法的,因为它们与默认的字符序比较是相等的(因此,是非唯一的)。如果你想在某一列或表上进行区分大小写的比较,请将该列或表改为使用 utf8_bin 字符序。

请注意,根据 MySQL Unicode 字符集utf8_general_ci 的比较比 utf8_unicode_ci 的比较要快,但正确率略低。如果这对你的应用是可以接受的,你应该使用 utf8_general_ci,因为它更快。如果不能接受(例如,如果你需要德语字典顺序),使用 utf8_unicode_ci,因为它更准确。

警告

模型表格集以区分大小写的方式验证唯一字段。因此,当使用不区分大小写的字符序方式时,一个具有唯一字段值的表单集,如果只因大小写不同,将通过验证,但在调用 save() 时,将引发 IntegrityError

Changed in Django 3.2:

增加了对配置字段的数据库字节序的支持。

连接数据库

参考 配置文档

连接配置应按此顺序使用

  1. OPTIONS
  2. NAMEUSERPASSWORDHOSTPORT
  3. MySQL 选项文件。

换句话说,如果你在 OPTIONS 中设置数据库的名称,这将优先于 NAME,它将覆盖 MySQL 选项文件 中的任何内容。

下面是一个使用 MySQL 选项文件的配置示例:

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '/path/to/my.cnf',
        },
    }
}


# my.cnf
[client]
database = NAME
user = USER
password = PASSWORD
default-character-set = utf8

其他几个 MySQLdb 连接选项 可能会有用,比如 sslinit_commandsql_mode

设置 sql_mode

从 MySQL 5.7 开始,sql_mode 选项的默认值包含 STRICT_TRANS_TABLES。该选项在插入数据时将警告升级为错误,因此 Django 强烈建议激活 MySQL 的 strict mode ,以防止数据丢失 (STRICT_TRANS_TABLESSTRICT_ALL_TABLES)。

如果你需要自定义 SQL 模式,你可以像其他 MySQL 选项一样设置 sql_mode 变量:可以在配置文件中设置,也可以在你的数据库配置的 OPTIONS 部分的 DATABASES 中使用 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" 配置。

隔离等级

当运行并发负载时,来自不同会话的数据库事务(例如,处理不同请求的独立线程)可能会相互交互。这些交互受到每个会话的 事务隔离级别 的影响。你可以在数据库配置的 DATABASES 中的 OPTIONS 部分设置连接的隔离级别,并在其中设置一个 'isolation_level' 条目。这个条目的有效值是四个标准隔离级别:

  • 'read uncommitted'
  • 'read committed'
  • 'repeatable read'
  • 'serializable'

None 来使用服务器配置的隔离级别。然而,Django 的最佳工作方式和默认值是 read committed,而不是 MySQL 的默认 repeatable read。在使用 repeatable read 时,可能会出现数据丢失的情况。特别是,你可能会看到这样的情况:get_or_create() 会引发一个 IntegrityError,但在随后的 get() 调用中不会出现该对象。

创建你的表

当 Django 生成架构时,它并没有指定存储引擎,所以无论你的数据库服务器配置了什么默认的存储引擎,都会创建表。最简单的解决方案是将数据库服务器的默认存储引擎设置为所需的引擎。

如果你使用的是托管服务,无法更改服务器的默认存储引擎,你有几个选择。

  • 在创建表后,执行 ALTER TABLE 语句,将表转换为新的存储引擎(如 InnoDB):

    ALTER TABLE <tablename> ENGINE=INNODB;
    

    如果你有很多表,这可能会很繁琐。

  • 另一个选择是在创建表之前使用 MySQLdb 的 init_command 选项:

    'OPTIONS': {
       'init_command': 'SET default_storage_engine=INNODB',
    }
    

    这将设置连接到数据库时的默认存储引擎。在你的表创建后,你应该删除这个选项,因为它为每个数据库连接添加了一个只在表创建期间需要的查询。

表名称

即使在最新版本的 MySQL 中,也有一些 已知问题 ,当在某些条件下执行某些 SQL 语句时,可能会导致表名的大小写被改变。如果可能的话,建议你使用小写的表名,以避免这种行为可能产生的任何问题。Django 在从模型中自动生成表名时使用小写表名,所以这主要是考虑到如果你是通过 db_table 参数来覆盖表名。

保存点

Django ORM 和 MySQL(使用 InnoDB 存储引擎 时)都支持数据库 保存点

如果你使用 MyISAM 存储引擎,请注意,如果你试图使用 事务 API 的保存点相关方法,你将收到数据库生成的错误。原因是检测 MySQL 数据库/表的存储引擎是一个昂贵的操作,所以决定不值得在没有操作的情况下动态转换这些方法,基于这种检测的结果。

特定字段的注意事项

字符字段

如果你对字段使用了 unique=True,那么任何以 VARCHAR 列类型存储的字段可能会被 max_length 限制为255个字符。这将影响 CharFieldSlugField。更多细节请看 MySQL 文档

TextField 限制

MySQL 只能对 BLOBTEXT 列的前 N 个字符进行索引。由于 TextField 没有定义的长度,所以不能将其标记为 unique=True。MySQL 会报告:"BLOB/TEXT column '<db_column>' used in key specification without a key length"。

支持时间和 DateTime 字段的小数秒。

MySQL 可以存储小数秒,只要列的定义包括一个小数指示(例如 DATETIME(6))。

如果数据库服务器支持的话,Django 将不会升级现有的列来包含小数秒。如果你想在现有的数据库上启用它们,就得在目标数据库上手动更新列,执行一个命令,比如:

ALTER TABLE `your_table` MODIFY `your_datetime_column` DATETIME(6)

或在 数据迁移 中使用 RunSQL 操作。

TIMESTAMP

如果你使用的是包含 TIMESTAMP 列的遗留数据库,你必须设置 USE_TZ = False 以避免数据损坏。 inspectdb 将这些列映射到 DateTimeField,如果你启用了时区支持,MySQL 和 Django 都会尝试将值从 UTC 转换为当地时间。

QuerySet.select_for_update() 锁定行

MySQL 和 MariaDB 不支持 SELECT ... FOR UPDATE 语句的某些选项。如果 select_for_update() 与一个不支持的选项一起使用,那么就会引发一个 NotSupportedError

选项 MariaDB MySQL
SKIP LOCKED   X (≥8.0.1)
NOWAIT X (≥10.3) X (≥8.0.1)
OF   X (≥8.0.1)
NO KEY    

When using select_for_update() on MySQL, make sure you filter a queryset against at least a set of fields contained in unique constraints or only against fields covered by indexes. Otherwise, an exclusive write lock will be acquired over the full table for the duration of the transaction.

自动排版会造成意想不到的结果

当对字符串类型执行查询,但有一个整数值时,MySQL 会在执行比较之前将表中所有值的类型强制为整数。如果你的表中包含值 'abc''def',而你查询 WHERE mycolumn=0,两行都会匹配。同理,WHERE mycolumn=1 将匹配值 'abc1'。因此,Django 中包含的字符串类型字段在查询中使用之前,总是会先将值转换为字符串。

如果你实现的自定义模型字段直接继承自 Field,覆盖 get_prep_value() 或者使用 RawSQLextra() 或者 raw(),你应该确保你执行了适当的类型化。

SQLite 注意事项

Django 支持 SQLite 3.9.0 及以上版本。

SQLite 为以只读为主或需要较小安装空间的应用程序提供了一个很好的开发选择。不过,与所有数据库服务器一样,SQLite 也有一些特定的差异,你应该注意。

子串匹配和大小写敏感性

对于所有的 SQLite 版本,当尝试匹配某些类型的字符串时,会出现一些略微反直觉的行为。 当在查询中使用 iexactcontains 过滤器时,这些行为会被触发。这种行为分为两种情况:

1. For substring matching, all matches are done case-insensitively. That is a filter such as filter(name__contains="aa") will match a name of "Aabb".

2. For strings containing characters outside the ASCII range, all exact string matches are performed case-sensitively, even when the case-insensitive options are passed into the query. So the iexact filter will behave exactly the same as the exact filter in these cases.

一些可能的变通方法在 sqlite.org 有记载 ,但是 Django 的默认 SQLite 后端并没有利用这些方法,因为将它们整合起来是相当困难的。因此,Django 暴露了默认的 SQLite 行为,当你进行大小写不敏感或子串过滤时,你应该注意这一点。

小数处理

SQLite 没有真正的小数内部类型。小数值在内部转换为 REAL 数据类型(8 字节的 IEEE 浮点数),正如 SQLite 数据类型文档 中所解释的那样,所以它们不支持正确舍入的小数浮点运算。

“Database is locked”错误

SQLite 是一个轻量级数据库,因此不能支持高并发。OperationalError: database is locked 错误表明你的应用程序遇到的并发量超过了 sqlite 在默认配置下所能处理的范围。这个错误意味着一个线程或进程在数据库连接上有一个独占锁,另一个线程超时等待锁被释放。

Python 的 SQLite 包装器有一个默认的超时值,这个超时值决定了第二个线程在超时并引发 OperationalError: database is locked 错误之前允许在锁上等待多长时间。

如果你遇到这种错误,你可以通过以下方式解决:

  • 切换到另一个数据库后端。到了一定程度,SQLite 对于现实世界的应用来说就会变得过于“精简”,这类并发错误表明你已经达到了这个程度。

  • 重写你的代码以减少并发性,并确保数据库事务是短暂的。

  • 通过设置 timeout 数据库选项增加默认超时值:

    'OPTIONS': {
        # ...
        'timeout': 20,
        # ...
    }
    

    这将使 SQLite 在抛出 “database is locked” 的错误之前等待更长的时间;它不会真正起到任何解决这些问题的作用。

不支持 QuerySet.select_for_update()

SQLite 不支持 SELECT ... FOR UPDATE 语法。调用它不会有任何效果。

不支持原始查询中的“pyformat”参数样式。

对于大多数后端来说,原始查询(Manager.raw()cursor.execute())可以使用“pyformat”参数风格,即查询中的占位符以 '%(name)s' 的形式给出,参数以字典而不是列表的形式传递。SQLite 不支持这种方式。

使用 QuerySet.iterator() 时的隔离

当使用 QuerySet.iterator() 在迭代表时修改表,有一些特殊的注意事项在 SQLite 的隔离 中描述。如果一条记录在循环中被添加、更改或删除,那么这条记录可能会出现,也可能不会出现,或者可能会在后续从迭代器中获取的结果中出现两次。你的代码必须处理这个问题。

在 SQLite 上启用 JSON1 扩展

要在 SQLite 上使用 JSONField,你需要在 Python 的 sqlite3 库中启用 JSON1 扩展 。如果在你的安装中没有启用扩展,系统会产生一个错误(fields.E180)。

要启用 JSON1 扩展,你可以按照 wiki 页面 上的说明进行操作。

Oracle 注意事项

Django 支持 Oracle 数据库服务器 12.2 及以上版本。需要 6.0 或更高版本的 cx_Oracle Python 驱动。

为了使 python manage.py migrate 命令有效,你的 Oracle 数据库用户必须拥有运行以下命令的权限:

  • CREATE TABLE
  • CREATE SEQUENCE
  • CREATE PROCEDURE
  • CREATE TRIGGER

要运行一个项目的测试套件,用户通常需要这些 额外 的权限:

  • CREATE USER
  • ALTER USER
  • DROP USER
  • CREATE TABLESPACE
  • DROP TABLESPACE
  • CREATE SESSION WITH ADMIN OPTION
  • CREATE TABLE WITH ADMIN OPTION
  • CREATE SEQUENCE WITH ADMIN OPTION
  • CREATE PROCEDURE WITH ADMIN OPTION
  • CREATE TRIGGER WITH ADMIN OPTION

虽然 RESOURCE 角色具有所需的 CREATE TABLECREATE SEQUENCECREATE PROCEDURECREATE TRIGGER 特权,被授予 RESOURCE WITH ADMIN OPTION 的用户可以授予 RESOURCE,但这样的用户不能授予单个特权(如 CREATE TABLE),因此 RESOURCE WITH ADMIN OPTION 通常不足以运行测试。

有些测试套件还可以创建视图或实体化视图;要运行这些视图,用户还需要 CREATE VIEW WITH ADMIN OPTIONCREATE MATERIALIZED VIEW WITH ADMIN OPTION 权限。尤其是 Django 自己的测试套件,更是需要这样的权限。

这些权限都包含在 DBA 角色中,适合在个人开发者的数据库上使用。

Oracle 数据库后台使用 SYS.DBMS_LOBSYS.DBMS_RANDOM 包,所以你的用户需要对它有执行权限。一般情况下,所有用户都可以访问它,但如果不是,你需要授予这样的权限。

GRANT EXECUTE ON SYS.DBMS_LOB TO user;
GRANT EXECUTE ON SYS.DBMS_RANDOM TO user;

连接数据库

要使用 Oracle 数据库的服务名进行连接,你的 settings.py 文件应该像这样:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': 'xe',
        'USER': 'a_user',
        'PASSWORD': 'a_password',
        'HOST': '',
        'PORT': '',
    }
}

在这种情况下,你应该把 HOSTPORT 都留空。但是,如果你不使用 tnsnames.ora 文件或类似的命名方法,而想使用 SID(本例中的“xe”)进行连接,则应像这样填写 HOSTPORT

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': 'xe',
        'USER': 'a_user',
        'PASSWORD': 'a_password',
        'HOST': 'dbprod01ned.mycompany.com',
        'PORT': '1540',
    }
}

你应该同时提供 HOSTPORT,或者把这两个字符串都留为空。Django 会根据这个选择使用不同的连接描述符。

完整的 DSN 和 Easy Connect

如果 HOSTPORT 都是空的,可以在 NAME 中使用完整的 DSN 或 Easy Connect 字符串。例如,在使用 RAC 或没有 tnsnames.ora 的可插拔数据库时,需要使用这种格式。

一个 Easy Connect 字符串的例子:

'NAME': 'localhost:1521/orclpdb1',

一个完整 DSN 字符串的例子:

'NAME': (
    '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))'
    '(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))'
),

线程选项

如果你打算在多线程环境中运行 Django(例如 Apache 在任何现代操作系统中使用默认的 MPM 模块),那么你 必须 将 Oracle 数据库配置中的 threaded 选项设置为 True

'OPTIONS': {
    'threaded': True,
},

如果不这样做,可能会导致崩溃和其他奇怪的行为。

INSERT ... RETURNING INTO

默认情况下,当插入新行时,Oracle 后端使用 RETURNING INTO 子句来有效地检索 AutoField 的值。 这种行为可能会在某些不寻常的设置中导致 DatabaseError,例如当插入到远程表中,或插入到具有 INSTEAD OF 触发器的视图中。可以通过将数据库配置中的 use_returning_into 选项设置为 False 来禁用 RETURNING INTO 子句:

'OPTIONS': {
    'use_returning_into': False,
},

在这种情况下,Oracle 后端将使用一个单独的 SELECT 查询来检索 AutoField 值。

命名问题

Oracle 规定名称长度限制为 30 个字符。为了适应这一限制,后端对数据库标识符进行截断以适应,用一个可重复的 MD5 哈希值替换截断后的名称的最后四个字符。此外,后端将数据库标识符变成全大写。

为了防止这些转换(通常只有在处理遗留数据库或访问属于其他用户的表时才需要这样做),使用加引号的名称作为 db_table

class LegacyModel(models.Model):
    class Meta:
        db_table = '"name_left_in_lowercase"'

class ForeignModel(models.Model):
    class Meta:
        db_table = '"OTHER_USER"."NAME_ONLY_SEEMS_OVER_30"'

引用的名字也可以用在 Django 的其他支持的数据库后端;但是,除了 Oracle,引用没有任何效果。

当运行 migrate 时,如果将某些 Oracle 关键字用作模型字段的名称或 db_column 选项的值,可能会遇到 ORA-06552 错误。 Django 引用了查询中使用的所有标识符,以防止大多数这样的问题,但当使用 Oracle 数据类型作为列名时,仍然会出现这个错误。 特别是要注意避免使用 datetimestampnumberfloat 作为字段名。

NULL 和空字符串

Django 通常更喜欢使用空字符串('')而不是 NULL,但 Oracle 对两者的处理是一样的。为了解决这个问题,Oracle 后端会忽略字段上显式的 null 选项,并将 null=True 生成 DDL。当从数据库中获取数据时,假定这些字段中的 null 值确实意味着空字符串,数据被默默地转换以反映这一假设。

TextField 限制

Oracle 后台将 TextFields 存储为 NCLOB 列。Oracle 对这种 LOB 列的使用一般有一些限制:

  • LOB 列不可作为主键使用。
  • LOB 列不可用于索引中。
  • LOB 列不能用于 SELECT DISTINCT 列表中。这意味着,当与 Oracle 运行时,试图在包含 TextField 列的模型上使用 QuerySet.distinct 方法将导致 ORA-00932 错误。作为一个变通办法,使用 QuerySet.defer 方法与 distinct() 结合使用,以防止 TextField 列被包含在 SELECT DISTINCT 列表中。

子类化内置数据库后端

Django 有内置的数据库后端,你可以对现有的数据库后端进行子类化来修改它的行为、功能或配置。

例如,考虑到你需要改变一个单一的数据库功能。首先,你必须创建一个新的目录,里面有一个 base 模块。例如:

mysite/
    ...
    mydbengine/
        __init__.py
        base.py

base.py 模块必须包含一个名为 DatabaseWrapper 的类,它从 django.db.backends 模块中子类化了一个现有的引擎。下面是一个子类化 PostgreSQL 引擎的例子,用来改变一个特征类 allows_group_by_selected_pks_on_model

mysite/mydbengine/base.py
from django.db.backends.postgresql import base, features

class DatabaseFeatures(features.DatabaseFeatures):
    def allows_group_by_selected_pks_on_model(self, model):
        return True

class DatabaseWrapper(base.DatabaseWrapper):
    features_class = DatabaseFeatures

最后,你必须在你的 settings.py 文件中指定一个 DATABASE-ENGINE

DATABASES = {
    'default': {
        'ENGINE': 'mydbengine',
        ...
    },
}

你可以在 django/db/backends 中查看当前的数据库引擎列表。

使用第三方数据库后端

除了官方支持的数据库外,还有第三方提供的后端,允许你在 Django 中使用其他数据库。

这些非官方后端所支持的 Django 版本和 ORM 功能有很大的不同。关于这些非官方后端的具体功能的查询,以及任何支持的查询,都应该通过每个第三方项目提供的支持渠道进行。

Back to Top