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

当试图将 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 具有 utf8 _ general _ ci 排序规则的 utf-8字符集。

更新命令的结果

在使用 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个字节,从而导致了上述错误?

139631 次浏览

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

user.last_name = u'Slatkevičius'

或者(如果没有字符串文字)使用 utf-8编码对它们进行解码:

user.last_name = lastname.decode('utf-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 表中,并在呈现到模板以供显示之前对其进行解码?

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

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

您可以将文本字段的排序规则更改为 UTF8 _ general _ ci,这样问题就解决了。

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

如果您有这个问题,这里有一个 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()

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

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

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

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

  1. 更改您的 MySQL 数据库、表和列以使用 Utf8mb4字符集(只能从 MySQL 5.5开始使用)
  2. 在 Django 设置文件中指定字符集如下:

设置

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

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

最有可能的原因是 CharField,它的 max _ length 为255,上面有某种索引(比如惟一索引)。因为 utf8mb4比 utf-8多使用33% 的空间,所以需要将这些字段缩小33% 。

在本例中,将 max _ length 从255更改为191。

你也可以选择 编辑您的 MySQL 配置以删除此限制 但不能没有姜戈的手法

更新: 我刚刚再次遇到这个问题,结束了 切换到 PostgreSQL,因为我无法减少我的 VARCHAR到191个字符。

如果是一个新项目,我会删除数据库,然后创建一个带有适当字符集的新项目:

CREATE DATABASE <dbname> CHARACTER SET utf8;

只需更改表,不需要任何东西。只需在数据库中运行此查询。 将表 table_name转换为字符集 utf8

一定会成功的。

作为 django 管理命令对@madprops answer-Solution 的改进:

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()


希望这个能帮到除了我以外的任何人:)