在Django中保存unicode字符串时MySQL出现“不正确的字符串值”错误
我在尝试将名字和姓氏保存到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 个回答
我之前也遇到过同样的问题,后来通过更改列的字符集解决了。虽然你的数据库默认字符集是 utf-8
,但我觉得在MySQL中,数据库的列可能会有不同的字符集。下面是我使用的SQL查询:
ALTER TABLE database.table MODIFY COLUMN col VARCHAR(255)
CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
这些回答都没能解决我的问题。根本原因是:
你不能在MySQL中使用utf-8字符集存储4字节的字符。
MySQL对utf-8字符有一个3字节的限制(是的,这很奇怪,这里有个Django开发者的总结)
要解决这个问题,你需要:
- 将你的MySQL数据库、表和列改为使用utf8mb4字符集(这个字符集从MySQL 5.5开始才有)
- 在你的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个字符。
我刚刚找到了一种方法,可以避免上面提到的错误。
保存到数据库
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表中,并在显示之前解码的方法呢?