---
title: "数据库"
version: 5.1
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/5.1/ref/databases/
canonical: https://djangodocs.dev/zh-hans/5.1/ref/databases/
---
# 数据库

Django 官方支持以下数据库：

- [PostgreSQL](#postgresql-notes)
- [MariaDB](#mariadb-notes)
- [MySQL](#mysql-notes)
- [Oracle](#oracle-notes)
- [SQLite](#sqlite-notes)

还有一些第三方提供的 [数据库后端](#third-party-notes)。

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

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

## 通用注意事项

### 持久连接

持久连接避免了在每个 HTTP 请求中重新建立与数据库的连接的开销。它们由 [`CONN_MAX_AGE`](/zh-hans/5.1/ref/settings/#std-setting-CONN_MAX_AGE) 参数控制，该参数定义了连接的最大生命周期。它可以独立设置在每个数据库上。

默认值是 `0`，保留了每次请求结束时关闭数据库连接的历史行为。要启用持久连接，可将 [`CONN_MAX_AGE`](/zh-hans/5.1/ref/settings/#std-setting-CONN_MAX_AGE) 设置为正整数秒。对于无限制的持久连接，将其设置为 `None`。

When using ASGI, persistent connections should be disabled. Instead, use your
database backend's built-in connection pooling if available, or investigate a
third-party connection pooling option if required.

#### 连接管理

当 Django 第一次进行数据库查询时，就会打开一个与数据库的连接。它保持这个连接的开放性，并在以后的请求中重复使用。一旦连接超过了 [`CONN_MAX_AGE`](/zh-hans/5.1/ref/settings/#std-setting-CONN_MAX_AGE) 所定义的最大时长，或者当它不再可用时，Django 就会关闭这个连接。

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

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

在每个请求结束时，如果连接已达到其最大寿命或处于不可恢复的错误状态，Django 将关闭连接。如果在处理请求时发生了任何数据库错误，Django 会检查连接是否仍然可用，如果不可用，将关闭连接。因此，数据库错误最多会影响每个应用程序工作线程的一个请求；如果连接变得无法使用，下一个请求将获得一个新的连接。

将 [`CONN_HEALTH_CHECKS`](/zh-hans/5.1/ref/settings/#std-setting-CONN_HEALTH_CHECKS) 设置为 `True` 可以用于提高连接复用的健壮性，并防止在连接已被数据库服务器关闭且现在已准备好接受和提供新连接时发生错误，例如在数据库服务器重新启动后。健康检查只在每个请求中执行一次，只有在处理请求期间访问数据库时才会执行。

#### 附加说明

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

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

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

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

如果在 Django 的请求-响应周期之外的长运行进程中创建了连接，该连接将保持打开状态，直到显式关闭或超时发生。你可以使用 `django.db.close_old_connections()` 来关闭所有旧的或不可用的连接。

### 编码

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

## PostgreSQL 注意事项

Django 支持 PostgreSQL 13 及以上版本。需要 [psycopg](https://www.psycopg.org/psycopg3/) 3.1.8+ 或 [psycopg2](https://www.psycopg.org/) 2.8.4+，但建议使用最新的 [psycopg](https://www.psycopg.org/psycopg3/) 3.1.8+。

> **Note**
>
> 对 `psycopg2` 的支持可能会在将来的某个时候被弃用并移除。

### PostgreSQL 连接配置

详见 [`HOST`](/zh-hans/5.1/ref/settings/#std-setting-HOST)。

要使用 [连接服务文件](https://www.postgresql.org/docs/current/libpq-pgservice.html) 中的服务名称和 [密码文件](https://www.postgresql.org/docs/current/libpq-pgpass.html) 中的密码进行连接，你必须在数据库配置中的  部分指定它们。

*`settings.py`*

```python
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "OPTIONS": {
            "service": "my_service",
            "passfile": ".my_pgpass",
        },
    }
}
```

*`.pg_service.conf`*

```text
[my_service]
host=localhost
user=USER
dbname=NAME
port=5432
```

*`.my_pgpass`*

```text
localhost:5432:NAME:USER:PASSWORD
```

PostgreSQL 后端将 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 的内容作为关键字参数传递给连接构造函数，从而允许更高级地控制驱动程序的行为。所有可用的 [parameters](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS) 在 PostgreSQL 文档中都有详细描述。

> **Warning**
>
> 出于测试目的不支持使用服务名称。这个 [可能会在以后实现](https://code.djangoproject.com/ticket/33685)。

### 优化 PostgreSQL 的配置

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

- `client_encoding`：`'UTF8'`，
- `default_transaction_isolation`：`'read committed'` 默认情况下，或在连接选项中设置的值（见下文）。
- **`timezone`：**

    - 当 [`USE_TZ`](/zh-hans/5.1/ref/settings/#std-setting-USE_TZ) 为 `True` 时，默认为 `'UTC`，或 [`TIME_ZONE`](/zh-hans/5.1/ref/settings/#std-setting-TIME_ZONE) 为连接设置的值。
    - 当 [`USE_TZ`](/zh-hans/5.1/ref/settings/#std-setting-USE_TZ) 为 `False` 时，全局 [`TIME_ZONE`](/zh-hans/5.1/ref/settings/#std-setting-TIME_ZONE) 设置的值。

如果这些参数已经有了正确的值，Django 就不会为每个新的连接设置这些参数，这样可以稍微提高性能。你可以直接在 `postgresql.conf` 中设置它们，或者更方便地在每个数据库用户中用 [ALTER ROLE](https://www.postgresql.org/docs/current/sql-alterrole.html) 设置。

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

### 隔离等级

和 PostgreSQL 本身一样，Django 默认为 `READ COMMITTED` [隔离级别](https://www.postgresql.org/docs/current/transaction-iso.html) 。如果你需要更高的隔离级别，比如 `REPEATABLE READ` 或者 `SERIALIZABLE`，可以在数据库配置的 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 中的 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 部分进行设置：

```
from django.db.backends.postgresql.psycopg_any import IsolationLevel

DATABASES = {
    # ...
    "OPTIONS": {
        "isolation_level": IsolationLevel.SERIALIZABLE,
    },
}
```

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

### 角色

If you need to use a different role for database connections than the role used
to establish the connection, set it in the [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) part of your
database configuration in [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES):

```
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        # ...
        "OPTIONS": {
            "assume_role": "my_application_role",
        },
    },
}
```

### 连接池

> **New in Django 5.1**

要将连接池与 [psycopg](https://www.psycopg.org/psycopg3/) 一起使用，你可以在 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 的数据库配置的 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 部分将 `"pool"` 设置为传递给 [`ConnectionPool`](https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.ConnectionPool) 的字典，或设置为 `True` 以使用 `ConnectionPool` 的默认值：

```
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        # ...
        "OPTIONS": {
            "pool": True,
        },
    },
}
```

此选项需要安装 `psycopg[pool]` 或 [psycopg-pool](https://pypi.org/project/psycopg-pool/)，并且在使用 `psycopg2` 时会被忽略。

### 服务器端参数绑定

使用 [psycopg](https://www.psycopg.org/psycopg3/) 3.1.8+，Django 默认使用 [客户端端绑定游标](https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#client-side-binding-cursors)。如果想要使用 [服务器端绑定](https://www.psycopg.org/psycopg3/docs/basic/from_pg2.html#server-side-binding)，请在 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 中的数据库配置的 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 部分进行设置：

```
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        # ...
        "OPTIONS": {
            "server_side_binding": True,
        },
    },
}
```

这个选项在 `psycopg2` 中被忽略。

### `varchar` 和 `text` 列的索引。

当指定 `db_index=True` 时，Django 通常会输出一条 `CREATE INDEX` 语句。 但是，如果字段的数据库类型是 `varchar` 或 `text` （例如，`CharField`、`FileField` 和 `TextField`），那么 Django 将为该列创建一个额外的索引，使用适当的 [PostgreSQL 操作类](https://www.postgresql.org/docs/current/indexes-opclass.html) 。 这个额外的索引对于正确执行在 SQL 中使用 `LIKE` 操作符的查询是必要的，就像 `contains` 和 `startswith` 查询类型一样。

### 添加扩展的迁移操作

如果你需要使用迁移来添加 PostgreSQL 扩展（如 `hstore`、`postgis` 等），请使用 [`CreateExtension`](/zh-hans/5.1/ref/contrib/postgres/operations/#django.contrib.postgres.operations.CreateExtension) 操作。

### 服务器端游标

在使用 [`QuerySet.iterator()`](/zh-hans/5.1/ref/models/querysets/#django.db.models.query.QuerySet.iterator) 时，Django 打开了一个 [服务器端游标](https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#server-side-cursors)。默认情况下，PostgreSQL 假定游标查询结果的前 10% 将被获取。查询计划器在规划查询时花费更少的时间，开始更快地返回结果，但如果获取的结果超过 10%，性能可能会下降。PostgreSQL 对游标查询的行数假设是由 [cursor\_tuple\_fraction](https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-CURSOR-TUPLE-FRACTION) 选项控制的。

#### 事务池和服务器端游标

在事务池模式下使用连接池（如 [PgBouncer](https://www.pgbouncer.org/) ）需要禁用该连接的服务器端游标。

服务器端游标是本地连接，当 [`AUTOCOMMIT`](/zh-hans/5.1/ref/settings/#std-setting-DATABASE-AUTOCOMMIT) 为 `True` 时，服务器端游标在事务结束时保持开放。后续事务可能会尝试从服务器端游标中获取更多的结果。在事务池模式下，不能保证后续事务会使用同一个连接。如果使用了不同的连接，当事务引用服务器端游标时就会出现错误，因为服务器端游标只有在创建它们的连接中才能访问。

一种解决方案是在 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 中通过将 [`DISABLE_SERVER_SIDE_CURSORS`](/zh-hans/5.1/ref/settings/#std-setting-DATABASE-DISABLE_SERVER_SIDE_CURSORS) 设置为 `True` 来禁用连接的服务器端游标。

为了从事务池模式下的服务器端游标中获益，你可以设置 [与数据库的另一个连接](/zh-hans/5.1/topics/db/multi-db/)，以便执行使用服务器端游标的查询。这个连接需要直接连接到数据库或者连接到会话池模式下的连接池。

另一种选择是将每个使用服务器端游标的 `QuerySet` 包裹在一个 [`atomic()`](/zh-hans/5.1/topics/db/transactions/#django.db.transaction.atomic) 块中，因为它在事务的持续时间内禁用 `autocommit`。这样一来，服务器端游标将只在事务持续时间内有效。

### 手动指定自增主键的值。

Django 使用 PostgreSQL 的 identity 列来存储自增的主键。一个 identity 列会通过一个 [sequence](https://www.postgresql.org/docs/current/sql-createsequence.html) 来填充值，该 sequence 跟踪下一个可用的值。手动为自增字段分配一个值不会更新字段的 sequence，这可能会导致后续冲突。例如：

```pycon
>>> 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`](/zh-hans/5.1/ref/django-admin/#django-admin-sqlsequencereset) 管理命令会生成 SQL 语句来做到这一点。

### 测试数据库模板

你可以使用 [`TEST['TEMPLATE']`](/zh-hans/5.1/ref/settings/#std-setting-TEST_TEMPLATE) 配置来指定一个 [template](https://www.postgresql.org/docs/current/sql-createdatabase.html) （例如 `'template0'`）来创建测试数据库。

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

你可以通过 [将 PostgreSQL 配置为非持久](https://www.postgresql.org/docs/current/non-durability.html) 来加快测试执行时间。

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

## MariaDB 注意事项

Django 支持 MariaDB 10.5 及以上版本。

要使用 MariaDB，请使用 MySQL 后端，两者共享。详情请看 [MySQL 注意事项](#mysql-notes)。

## MySQL 注意事项

### 版本支持

Django 支持 MySQL 8.0.11 及更高版本。

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

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

### 存储引擎

MySQL 有几个 [存储引擎](#storage-engines) 。你可以在服务器配置中更改默认的存储引擎。

MySQL 的默认存储引擎是 [InnoDB](https://dev.mysql.com/doc/refman/en/innodb-storage-engine.html) 。这个引擎是完全事务性的，并且支持外键引用。这是推荐的选择。然而，InnoDB 自动增量计数器在 MySQL 重启时丢失，因为它不记得 `AUTO_INCREMENT` 值，而是将其重新创建为 "max(id)+1"。这可能会导致无意中重用 [`AutoField`](/zh-hans/5.1/ref/models/fields/#django.db.models.AutoField) 值。

[MyISAM](https://dev.mysql.com/doc/refman/en/myisam-storage-engine.html) 的主要缺点是不支持事务，也不执行外键约束。

### MySQL 数据库 API 驱动程序

MySQL 有几个驱动程序实现了 [**PEP 249**](https://peps.python.org/pep-0249/) 中描述的 Python 数据库 API。

- [mysqlclient](https://pypi.org/project/mysqlclient/) 是一个本地的数据库驱动程序。它是 **推荐的选择**。
- [MySQL Connector/Python](https://dev.mysql.com/downloads/connector/python/) 是一个来自 Oracle 的纯 Python 驱动，不需要 MySQL 客户端库或标准库之外的任何 Python 模块。

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

除了数据库 API 驱动之外，Django 还需要一个适配器来从其 ORM 中访问数据库驱动。Django 为 mysqlclient 提供了一个适配器，而 MySQL Connector/Python 则包含了 [自己的](https://dev.mysql.com/doc/connector-python/en/connector-python-django-backend.html) 。

#### mysqlclient

Django 需要 [mysqlclient](#mysqlclient) 的版本为 1.4.3 或更高。

#### MySQL Connector/Python

MySQL Connector/Python 可从 [下载页面](https://dev.mysql.com/downloads/connector/python/) 。Django 适配器在 1.1.X 及以后的版本中可用。它可能不支持最新版本的 Django。

### 时区定义

如果你打算使用 Django 的 [时区支持](/zh-hans/5.1/topics/i18n/timezones/)，使用 [mysql\_tzinfo\_to\_sql](https://dev.mysql.com/doc/refman/en/mysql-tzinfo-to-sql.html) 将时区表加载到 MySQL 数据库中。这只需要为你的 MySQL 服务器做一次，而不是每个数据库。

### 创建你的数据库

你可以使用命令行工具并执行以下 SQL 语句来 [创建数据库](https://dev.mysql.com/doc/refman/en/create-database.html)：

```sql
CREATE DATABASE <dbname> CHARACTER SET utf8;
```

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

#### 字符序配置

一列的字符序配置控制了数据排序的顺序，以及哪些字符串被比较为相等。你可以指定 `db_collation` 参数来为 [`CharField`](/zh-hans/5.1/ref/models/fields/#django.db.models.CharField.db_collation) 和 [`TextField`](/zh-hans/5.1/ref/models/fields/#django.db.models.TextField.db_collation) 设置列的字符序名称。

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

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

请注意，根据 [MySQL Unicode 字符集](https://dev.mysql.com/doc/refman/en/charset-unicode-sets.html) ，`utf8_general_ci` 的比较比 `utf8_unicode_ci` 的比较要快，但正确率略低。如果这对你的应用是可以接受的，你应该使用 `utf8_general_ci`，因为它更快。如果不能接受（例如，如果你需要德语字典顺序），使用 `utf8_unicode_ci`，因为它更准确。

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

### 连接数据库

参考 [配置文档](/zh-hans/5.1/ref/settings/)。

连接配置应按此顺序使用

1. [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS)。
2. [`NAME`](/zh-hans/5.1/ref/settings/#std-setting-NAME)、[`USER`](/zh-hans/5.1/ref/settings/#std-setting-USER)、[`PASSWORD`](/zh-hans/5.1/ref/settings/#std-setting-PASSWORD)、[`HOST`](/zh-hans/5.1/ref/settings/#std-setting-HOST)、[`PORT`](/zh-hans/5.1/ref/settings/#std-setting-PORT)
3. MySQL 选项文件。

换句话说，如果你在 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 中设置数据库的名称，这将优先于 [`NAME`](/zh-hans/5.1/ref/settings/#std-setting-NAME)，它将覆盖 [MySQL 选项文件](https://dev.mysql.com/doc/refman/en/option-files.html) 中的任何内容。

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

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

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

其他几个 [MySQLdb 连接选项](https://mysqlclient.readthedocs.io/user_guide.html#functions-and-attributes) 可能会有用，比如 `ssl`、`init_command` 和 `sql_mode`。

#### 设置 `sql_mode`

`sql_mode` 选项的默认值包含 `STRICT_TRANS_TABLES`。该选项在插入时将警告升级为错误，因此 Django 强烈建议在 MySQL 中激活 [strict mode](https://dev.mysql.com/doc/refman/en/sql-mode.html#sql-mode-strict)，以防止数据丢失（可以使用 `STRICT_TRANS_TABLES` 或 `STRICT_ALL_TABLES`）。

如果你需要自定义 SQL 模式，你可以像其他 MySQL 选项一样设置 `sql_mode` 变量：可以在配置文件中设置，也可以在你的数据库配置的 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 部分的 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 中使用 `'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"` 配置。

#### 隔离等级

当运行并发负载时，来自不同会话的数据库事务（例如，处理不同请求的独立线程）可能会相互交互。这些交互受到每个会话的 [事务隔离级别](https://dev.mysql.com/doc/refman/en/innodb-transaction-isolation-levels.html) 的影响。你可以在数据库配置的 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 中的 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 部分设置连接的隔离级别，并在其中设置一个 `'isolation_level'` 条目。这个条目的有效值是四个标准隔离级别：

- `'read uncommitted'`
- `'read committed'`
- `'repeatable read'`
- `'serializable'`

或 `None` 来使用服务器配置的隔离级别。然而，Django 的最佳工作方式和默认值是 read committed，而不是 MySQL 的默认 repeatable read。在使用 repeatable read 时，可能会出现数据丢失的情况。特别是，你可能会看到这样的情况：`get_or_create()` 会引发一个 [`IntegrityError`](/zh-hans/5.1/ref/exceptions/#django.db.IntegrityError)，但在随后的 `get()` 调用中不会出现该对象。

### 创建你的表

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

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

- 在创建表之后，执行一个 `ALTER TABLE` 语句来将表转换为新的存储引擎（例如 InnoDB）：

  ```sql
  ALTER TABLE <tablename> ENGINE=INNODB;
  ```

  如果你有很多表，这可能会很繁琐。
- 另一个选择是在创建表之前使用 MySQLdb 的 `init_command` 选项：

  ```
  "OPTIONS": {
      "init_command": "SET default_storage_engine=INNODB",
  }
  ```

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

### 表名称

即使在最新版本的 MySQL 中，也有一些 [已知问题](https://bugs.mysql.com/bug.php?id=48875) ，当在某些条件下执行某些 SQL 语句时，可能会导致表名的大小写被改变。如果可能的话，建议你使用小写的表名，以避免这种行为可能产生的任何问题。Django 在从模型中自动生成表名时使用小写表名，所以这主要是考虑到如果你是通过 [`db_table`](/zh-hans/5.1/ref/models/options/#django.db.models.Options.db_table) 参数来覆盖表名。

### 保存点

Django ORM 和 MySQL（使用 InnoDB [存储引擎](#mysql-storage-engines) 时）都支持数据库 [保存点](/zh-hans/5.1/topics/db/transactions/#topics-db-transactions-savepoints)。

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

### 特定字段的注意事项

#### 字符字段

如果你对字段使用了 `unique=True`，那么任何以 `VARCHAR` 列类型存储的字段可能会被 `max_length` 限制为255个字符。这将影响 [`CharField`](/zh-hans/5.1/ref/models/fields/#django.db.models.CharField)、[`SlugField`](/zh-hans/5.1/ref/models/fields/#django.db.models.SlugField)。更多细节请看 [MySQL 文档](https://dev.mysql.com/doc/refman/en/create-index.html#create-index-column-prefixes) 。

#### `TextField` 限制

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

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

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

Django 不会在数据库服务器支持的情况下升级现有列以包含小数秒。如果要在现有数据库上启用它们，你需要手动在目标数据库上更新列，通过执行类似以下命令：

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

或在 [数据迁移](/zh-hans/5.1/topics/migrations/#data-migrations) 中使用 [`RunSQL`](/zh-hans/5.1/ref/migration-operations/#django.db.migrations.operations.RunSQL) 操作。

#### `TIMESTAMP` 列

如果你使用的是包含 `TIMESTAMP` 列的遗留数据库，你必须设置 [`USE_TZ = False`](/zh-hans/5.1/ref/settings/#std-setting-USE_TZ) 以避免数据损坏。 [`inspectdb`](/zh-hans/5.1/ref/django-admin/#django-admin-inspectdb) 将这些列映射到 [`DateTimeField`](/zh-hans/5.1/ref/models/fields/#django.db.models.DateTimeField)，如果你启用了时区支持，MySQL 和 Django 都会尝试将值从 UTC 转换为当地时间。

### 用 `QuerySet.select_for_update()` 锁定行

MySQL 和 MariaDB 不支持 `SELECT ... FOR UPDATE` 语句的某些选项。如果 `select_for_update()` 与一个不支持的选项一起使用，那么就会引发一个 [`NotSupportedError`](/zh-hans/5.1/ref/exceptions/#django.db.NotSupportedError)。

| 选项 | MariaDB | MySQL |
| --- | --- | --- |
| `SKIP LOCKED` | X (≥10.6) | X |
| `NOWAIT` | X | X |
| `OF` |  | X |
| `NO KEY` |  |  |

当在 MySQL 上使用 `select_for_update()` 时，确保你至少针对唯一约束中包含的一组字段或仅针对索引覆盖的字段过滤查询集。否则，在事务过程中，将对整个表获得一个独占的写锁。

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

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

如果你实现的自定义模型字段直接继承自 [`Field`](/zh-hans/5.1/ref/models/fields/#django.db.models.Field)，覆盖 [`get_prep_value()`](/zh-hans/5.1/ref/models/fields/#django.db.models.Field.get_prep_value) 或者使用 `RawSQL`、`extra()` 或者 [`raw()`](/zh-hans/5.1/topics/db/sql/#django.db.models.Manager.raw)，你应该确保你执行了适当的类型化。

## SQLite 注意事项

Django 支持 SQLite 3.31.0 及更高版本。

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

### 子串匹配和大小写敏感性

对于所有 SQLite 版本，在尝试匹配某些类型的字符串时，存在一些稍微违反直觉的行为。这些行为在使用 Querysets 中的 [`iexact`](/zh-hans/5.1/ref/models/querysets/#std-fieldlookup-iexact) 或 [`contains`](/zh-hans/5.1/ref/models/querysets/#std-fieldlookup-contains) 过滤器时触发。行为分为两种情况：

\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`](/zh-hans/5.1/ref/models/querysets/#std-fieldlookup-iexact) filter will behave exactly
the same as the [`exact`](/zh-hans/5.1/ref/models/querysets/#std-fieldlookup-exact) filter in these cases.

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

### 小数处理

SQLite 没有真正的小数内部类型。小数值在内部转换为 `REAL` 数据类型（8 字节的 IEEE 浮点数），正如 [SQLite 数据类型文档](https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes) 中所解释的那样，所以它们不支持正确舍入的小数浮点运算。

### “Database is locked”错误

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

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

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

- 切换到另一个数据库后端。到了一定程度，SQLite 对于现实世界的应用来说就会变得过于“精简”，这类并发错误表明你已经达到了这个程度。
- 重写你的代码以减少并发性，并确保数据库事务是短暂的。
- 通过设置 `timeout` 数据库选项增加默认超时值：

  ```
  "OPTIONS": {
      # ...
      "timeout": 20,
      # ...
  }
  ```

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

#### 事务行为

> **New in Django 5.1**

SQLite 支持三种事务模式：`DEFERRED`、`IMMEDIATE` 和 `EXCLUSIVE`。

默认是 `DEFERRED`。如果你需要使用不同的模式，请在 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 的数据库配置的 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 部分设置它，例如：

```
"OPTIONS": {
    # ...
    "transaction_mode": "IMMEDIATE",
    # ...
}
```

为了确保你的事务在引发“数据库被锁定”之前等待 `timeout`，请将事务模式更改为 `IMMEDIATE`。

为了在 `IMMEDIATE` 和 `EXCLUSIVE` 模式下获得最佳性能，事务应尽可能短。这可能很难保证所有视图都满足，因此在这种情况下不鼓励使用 [`ATOMIC_REQUESTS`](/zh-hans/5.1/ref/settings/#std-setting-DATABASE-ATOMIC_REQUESTS)。

有关更多信息，请参阅 [SQLite 中的事务](https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions)。

### 不支持 `QuerySet.select_for_update()`

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

### 使用 `QuerySet.iterator()` 时的隔离

当使用 [`QuerySet.iterator()`](/zh-hans/5.1/ref/models/querysets/#django.db.models.query.QuerySet.iterator) 在迭代表时修改表，有一些特殊的注意事项在 [SQLite 的隔离](https://www.sqlite.org/isolation.html) 中描述。如果一条记录在循环中被添加、更改或删除，那么这条记录可能会出现，也可能不会出现，或者可能会在后续从迭代器中获取的结果中出现两次。你的代码必须处理这个问题。

### 在 SQLite 上启用 JSON1 扩展

要在 SQLite 上使用 [`JSONField`](/zh-hans/5.1/ref/models/fields/#django.db.models.JSONField)，你需要在 Python 的 [`sqlite3`](https://docs.python.org/3/library/sqlite3.html#module-sqlite3) 库中启用 [JSON1 扩展](https://www.sqlite.org/json1.html) 。如果在你的安装中没有启用扩展，系统会产生一个错误（`fields.E180`）。

要启用 JSON1 扩展，你可以按照 [wiki 页面](https://code.djangoproject.com/wiki/JSON1Extension) 上的说明进行操作。

> **Note**
>
> JSON1 扩展在 SQLite 3.38+ 上默认启用。

### 设置 pragma 选项

> **New in Django 5.1**

可以通过在 [`DATABASES`](/zh-hans/5.1/ref/settings/#std-setting-DATABASES) 的数据库配置的 [`OPTIONS`](/zh-hans/5.1/ref/settings/#std-setting-OPTIONS) 部分中使用 `init_command` 来在连接时设置 [Pragma 选项](https://www.sqlite.org/pragma.html)。以下示例展示了如何启用同步写入的额外持久性并更改 `cache_size`：

```
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        # ...
        "OPTIONS": {
            "init_command": "PRAGMA synchronous=3; PRAGMA cache_size=2000;",
        },
    }
}
```

## Oracle 注意事项

Django supports [Oracle Database Server](https://www.oracle.com/) versions 19c and higher. Version
1.3.2 through 3.3.0 of the [oracledb](https://oracle.github.io/python-oracledb/) Python driver is required.

> **Deprecated since Django 5.0**
>
> Deprecated since version 5.0: 对于 `cx_Oracle` 的支持已被弃用。

为了使 `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 TABLE`、`CREATE SEQUENCE`、`CREATE PROCEDURE` 和 `CREATE TRIGGER` 特权，被授予 `RESOURCE WITH ADMIN OPTION` 的用户可以授予 `RESOURCE`，但这样的用户不能授予单个特权（如 `CREATE TABLE`），因此 `RESOURCE WITH ADMIN OPTION` 通常不足以运行测试。

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

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

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

```sql
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": "",
    }
}
```

在这种情况下，你应该把 [`HOST`](/zh-hans/5.1/ref/settings/#std-setting-HOST) 和 [`PORT`](/zh-hans/5.1/ref/settings/#std-setting-PORT) 都留空。但是，如果你不使用 `tnsnames.ora` 文件或类似的命名方法，而想使用 SID（本例中的“xe”）进行连接，则应像这样填写 [`HOST`](/zh-hans/5.1/ref/settings/#std-setting-HOST) 和 [`PORT`](/zh-hans/5.1/ref/settings/#std-setting-PORT)。

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

你应该同时提供 [`HOST`](/zh-hans/5.1/ref/settings/#std-setting-HOST) 和 [`PORT`](/zh-hans/5.1/ref/settings/#std-setting-PORT)，或者把这两个字符串都留为空。Django 会根据这个选择使用不同的连接描述符。

#### 完整的 DSN 和 Easy Connect

如果 [`HOST`](/zh-hans/5.1/ref/settings/#std-setting-HOST) 和 [`PORT`](/zh-hans/5.1/ref/settings/#std-setting-PORT) 都是空的，可以在 [`NAME`](/zh-hans/5.1/ref/settings/#std-setting-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 数据类型作为列名时，仍然会出现这个错误。 特别是要注意避免使用 `date`、`timestamp`、`number` 或 `float` 作为字段名。

### 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` 模块的新目录，例如：

```text
mysite/
    ...
    mydbengine/
        __init__.py
        base.py
```

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

*`mysite/mydbengine/base.py`*

```python
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`](/zh-hans/5.1/ref/settings/#std-setting-DATABASE-ENGINE)：

```
DATABASES = {
    "default": {
        "ENGINE": "mydbengine",
        # ...
    },
}
```

你可以在 [django/db/backends](https://github.com/django/django/blob/stable/5.1.x/django/db/backends) 中查看当前的数据库引擎列表。

## 使用第三方数据库后端

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

- [CockroachDB](https://pypi.org/project/django-cockroachdb/)
- [Firebird](https://pypi.org/project/django-firebird/)
- [Google Cloud Spanner](https://pypi.org/project/django-google-spanner/)
- [Microsoft SQL Server](https://pypi.org/project/mssql-django/)
- [Snowflake](https://pypi.org/project/django-snowflake/)
- [TiDB](https://pypi.org/project/django-tidb/)
- [YugabyteDB](https://pypi.org/project/django-yugabytedb/)

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