在Django中保存unicode字符串时MySQL出现“不正确的字符串值”错误

173 投票
9 回答
143055 浏览
提问于 2025-04-15 18:18

我在尝试将名字和姓氏保存到Django的用户模型时,遇到了奇怪的错误信息。

失败的例子

user = User.object.create_user(username, email, password)
user.first_name = u'Rytis'
user.last_name = u'Slatkevičius'
user.save()
>>> Incorrect string value: '\xC4\x8Dius' for column 'last_name' at row 104

user.first_name = u'Валерий'
user.last_name = u'Богданов'
user.save()
>>> Incorrect string value: '\xD0\x92\xD0\xB0\xD0\xBB...' for column 'first_name' at row 104

user.first_name = u'Krzysztof'
user.last_name = u'Szukiełojć'
user.save()
>>> Incorrect string value: '\xC5\x82oj\xC4\x87' for column 'last_name' at row 104

成功的例子

user.first_name = u'Marcin'
user.last_name = u'Król'
user.save()
>>> SUCCEED

MySQL设置

mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       | 
| character_set_connection | utf8                       | 
| character_set_database   | utf8                       | 
| character_set_filesystem | binary                     | 
| character_set_results    | utf8                       | 
| character_set_server     | utf8                       | 
| character_set_system     | utf8                       | 
| character_sets_dir       | /usr/share/mysql/charsets/ | 
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

表的字符集和排序规则

auth_user表使用的是utf-8字符集,排序规则是utf8_general_ci。

更新命令的结果

在使用UPDATE命令更新上述值到auth_user表时,没有出现任何错误。

mysql> update auth_user set last_name='Slatkevičiusa' where id=1;
Query OK, 1 row affected, 1 warning (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select last_name from auth_user where id=100;
+---------------+
| last_name     |
+---------------+
| Slatkevi?iusa | 
+---------------+
1 row in set (0.00 sec)

PostgreSQL

当我在Django中切换数据库后,以上失败的值可以成功更新到PostgreSQL表中。这真奇怪。

mysql> SHOW CHARACTER SET;
+----------+-----------------------------+---------------------+--------+
| Charset  | Description                 | Default collation   | Maxlen |
+----------+-----------------------------+---------------------+--------+
...
| utf8     | UTF-8 Unicode               | utf8_general_ci     |      3 | 
...

但是在http://www.postgresql.org/docs/8.1/interactive/multibyte.html上,我发现了以下内容:

Name Bytes/Char
UTF8 1-4

这是否意味着在PostgreSQL中,unicode字符的最大长度是4个字节,而在MySQL中是3个字节,这导致了上述错误?

9 个回答

127

我之前也遇到过同样的问题,后来通过更改列的字符集解决了。虽然你的数据库默认字符集是 utf-8,但我觉得在MySQL中,数据库的列可能会有不同的字符集。下面是我使用的SQL查询:

    ALTER TABLE database.table MODIFY COLUMN col VARCHAR(255)
    CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
172

这些回答都没能解决我的问题。根本原因是:

你不能在MySQL中使用utf-8字符集存储4字节的字符。

MySQL对utf-8字符有一个3字节的限制(是的,这很奇怪,这里有个Django开发者的总结

要解决这个问题,你需要:

  1. 将你的MySQL数据库、表和列改为使用utf8mb4字符集(这个字符集从MySQL 5.5开始才有)
  2. 在你的Django设置文件中指定字符集,如下所示:

settings.py

DATABASES = {
    'default': {
        'ENGINE':'django.db.backends.mysql',
        ...
        'OPTIONS': {'charset': 'utf8mb4'},
    }
}

注意:当你重新创建数据库时,可能会遇到'指定的键太长'的问题。

最可能的原因是一个CharField字段的最大长度是255,并且在它上面有某种索引(例如唯一索引)。因为utf8mb4比utf-8多占用33%的空间,所以你需要把这些字段的长度缩小33%。

在这种情况下,把最大长度从255改为191。

另外,你也可以编辑你的MySQL配置来去掉这个限制但这需要一些Django的技巧

更新:我最近又遇到了这个问题,最后切换到了PostgreSQL,因为我无法把我的VARCHAR字段缩减到191个字符。

8

我刚刚找到了一种方法,可以避免上面提到的错误。

保存到数据库

user.first_name = u'Rytis'.encode('unicode_escape')
user.last_name = u'Slatkevičius'.encode('unicode_escape')
user.save()
>>> SUCCEED

print user.last_name
>>> Slatkevi\u010dius
print user.last_name.decode('unicode_escape')
>>> Slatkevičius

这是不是唯一一种将字符串保存到MySQL表中,并在显示之前解码的方法呢?

撰写回答