在Django中保存Unicode字符串时,MySQL“字符串值不正确”错误


158

尝试将first_name,last_name保存到Django的auth_user模型时,出现奇怪的错误消息。

失败的例子

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命令的结果

使用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

这是否意味着unicode char在PostgreSQL中的maxlen为4个字节,而在MySQL中为3个字节,这导致了上述错误?


2
这是一个MySQL的问题,而不是Django的:stackoverflow.com/questions/1168036/...
Vanuan

Answers:


139

这些答案都没有为我解决问题。根本原因是:

您不能在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'},
    }
}

注意:重新创建数据库时,您可能会遇到“ 指定密钥太长 ”的问题。

最可能的原因是a CharField,它的max_length为255,上面有某种索引(例如,唯一)。由于utf8mb4比utf-8使用的空间多33%,因此您需要将这些字段缩小33%。

在这种情况下,请将max_length从255更改为191。

或者,您可以编辑MySQL配置以消除此限制, 但是要注意一些django hackery

更新:我只是再次遇到这个问题,最终因为无法将我的字符数减少到191个而切换到PostgreSQLVARCHAR


13
这个答案需要更多的方式。谢谢!真正的问题是,在有人尝试输入4字节字符之前,您的应用程序可能会正常运行数年。
Michael Bylstra 2014年

2
这绝对是正确的答案。OPTIONS设置对于使Django解码表情符号字符并将其存储在MySQL中至关重要。仅通过SQL命令将mysql charset更改为utf8mb4是不够的!
Xerion

无需将整个表的字符集更新为utf8mb4。只需更新必要列的字符集即可。'charset': 'utf8mb4'正如@Xerion所说,Django设置中的选项也很关键。最后,索引问题是一团糟。删除列上的索引,或者使其长度不超过191,或者改用a TextField
Rockallite's

2
我喜欢您引用这句话的链接这只是MySQL有目的地且不可逆转地损坏大脑的另一种情况。:)
Qback

120

我遇到了同样的问题,并通过更改列的字符集解决了它。即使您的数据库具有默认字符集,utf-8我也认为数据库列在MySQL中可能具有不同的字符集。这是我使用的SQL查询:

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

14
gh,我更改了所有字符集,直到我重新阅读此答案为止:可以有自己字符集,独立于表和数据库。那太疯狂了,这也是我的问题。
markpasc 2011年

1
这也对我有用,在TextField模型中使用带有默认值的mysql。
madprops 2011年

这解决了我的问题。我所做的唯一更改是使用utf8mb4和utf8mb4_general_ci而不是utf8 / utf8_general_ci。
Michal Przysucha

70

如果您有此问题,请使用以下python脚本自动更改mysql数据库的所有列。

#! /usr/bin/env python
import MySQLdb

host = "localhost"
passwd = "passwd"
user = "youruser"
dbname = "yourdbname"

db = MySQLdb.connect(host=host, user=user, passwd=passwd, db=dbname)
cursor = db.cursor()

cursor.execute("ALTER DATABASE `%s` CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci'" % dbname)

sql = "SELECT DISTINCT(table_name) FROM information_schema.columns WHERE table_schema = '%s'" % dbname
cursor.execute(sql)

results = cursor.fetchall()
for row in results:
  sql = "ALTER TABLE `%s` convert to character set DEFAULT COLLATE DEFAULT" % (row[0])
  cursor.execute(sql)
db.close()

4
该解决方案使用存储文件和目录路径的django应用解决了我的所有问题。将dbname作为django数据库,并使其运行。像魅力一样工作!
克里斯(Chris)

1
在我之前添加db.commit()之前,这段代码对我不起作用db.close()
Mark Erdmann

1
此解决方案是否避免了@markpasc注释中讨论的问题:“ ... 4字节UTF-8字符,例如MySQL 5.1 3字节utf8字符集中的表情符号”
CatShoes

当我删除记录槽django admin时,解决方案对我有帮助,创建o编辑时我没有任何问题...很奇怪!我什至能够直接在数据库中删除
Javier Vieira

每次更改模型时都应该这样做吗?
Vanuan 2014年

25

如果这是一个新项目,则只需删除数据库,然后使用适当的字符集创建一个新项目:

CREATE DATABASE <dbname> CHARACTER SET utf8;

您好,请帮助检查此问题stackoverflow.com/questions/46348817/…–
国王

就我而言,我们的数据库是由docker创建的,因此要修复,我在撰写文件中的db:command:指令中添加了以下内容:- --character-set-server=utf8
followben

1
就如此容易。感谢@Vanuan
Enku

如果这不是一个新项目,我们将从db获取备份,将其删除并使用utf8 charset重新创建它,然后还原备份。我是在我的新项目中做到的
Mohammad Reza

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表中并在渲染为模板进行显示之前对其进行解码的唯一方法吗?


12
我遇到类似的问题,但是我不同意这是一个有效的解决方案。当您.encode('unicode_escape')实际上不在数据库中存储Unicode字符时。您正在强迫所有客户端在使用它们之前先对其进行解密,这意味着它无法与django.admin或其他各种方式正常工作。
muudscope

3
虽然存储转义代码而不是字符似乎很令人讨厌,但这可能是在MySQL 5.1的3字节utf8字符集中保存4字节UTF-8字符(例如emoji表情)的几种方法之一。
markpasc 2012年

2
有一种叫做“编码”的编码utf8mb4,它可以存储“基本多语言平面”以外的内容。我知道,您会认为“ UTF8”是完全存储Unicode所需要的。好吧,whaddaya知道,不是。参见dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
Mihai Danila 2013年

@jack,您可能需要考虑将接受的答案更改为更有用的答案
donturner 2013年

这是一个可行的解决方法,但我也不建议也使用它(@muudscope提倡)。我仍然无法将表情符号存储到mysql数据库中。有人做到了吗?
Marcelo Sardelich 2014年

6

您可以将文本字段的排序规则更改为UTF8_general_ci,此问题将得到解决。

注意,这不能在Django中完成。


1

您不是要保存unicode字符串,而是要以UTF-8编码保存字节字符串。使它们成为实际的unicode字符串文字:

user.last_name = u'Slatkevičius'

或(当您没有字符串文字时)使用utf-8编码对其进行解码:

user.last_name = lastname.decode('utf-8')

@托马斯,我尝试了完全按照您的说法,但是仍然会出现相同的错误。
杰克


0

改进@madprops答案-解决方案作为Django管理命令:

import MySQLdb
from django.conf import settings

from django.core.management.base import BaseCommand


class Command(BaseCommand):

    def handle(self, *args, **options):
        host = settings.DATABASES['default']['HOST']
        password = settings.DATABASES['default']['PASSWORD']
        user = settings.DATABASES['default']['USER']
        dbname = settings.DATABASES['default']['NAME']

        db = MySQLdb.connect(host=host, user=user, passwd=password, db=dbname)
        cursor = db.cursor()

        cursor.execute("ALTER DATABASE `%s` CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci'" % dbname)

        sql = "SELECT DISTINCT(table_name) FROM information_schema.columns WHERE table_schema = '%s'" % dbname
        cursor.execute(sql)

        results = cursor.fetchall()
        for row in results:
            print(f'Changing table "{row[0]}"...')
            sql = "ALTER TABLE `%s` convert to character set DEFAULT COLLATE DEFAULT" % (row[0])
            cursor.execute(sql)
        db.close()

希望这可以帮助我以外的任何人:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.