MariaDB 和 MySQL 注意事项
版本支持
Django 支持 MySQL 8.0.11 及更高版本。
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.3 或更高。
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 参数来为 CharField 和 TextField 设置列的字符序名称。
字符序也可以在整个数据库层面和每张表上设置。这在 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
。
连接数据库
连接配置应按此顺序使用
- OPTIONS。
- NAME、USER、PASSWORD、HOST、PORT
- 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 连接选项 可能会有用,比如 ssl、init_command 和 sql_mode。
设置 sql_mode
sql_mode
选项的默认值包含 STRICT_TRANS_TABLES
。该选项在插入时将警告升级为错误,因此 Django 强烈建议在 MySQL 中激活 strict_mode,以防止数据丢失(可以使用 STRICT_TRANS_TABLES
或 STRICT_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个字符。这将影响 CharField、SlugField。
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 不会在数据库服务器支持的情况下升级现有列以包含小数秒。如果要在现有数据库上启用它们,你需要手动在目标数据库上更新列,通过执行类似以下命令:
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 (≥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,覆盖 get_prep_value() 或者使用 RawSQL、extra() 或者 raw(),你应该确保你执行了适当的类型化。