在Django中保存unicode字符串时,MySQL“错误的字符串值”错误

时间:2010-01-21 11:34:23

标签: python mysql django unicode utf-8

我试图将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中有4个字节的maxlen但在MySQL中有3个字节导致上述错误?

9 个答案:

答案 0 :(得分:112)

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

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

答案 1 :(得分:109)

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

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

MySQL有一个3 byte limit on utf-8 characters(是的,它是wack,nicely summed up by a Django developer here

要解决此问题,您需要:

  1. 更改您的MySQL数据库,表和列以使用utf8mb4 character set(仅从MySQL 5.5开始提供)
  2. 在Django设置文件中指定charset,如下所示:
  3. <强> settings.py

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

    注意:重新创建数据库时,可能会遇到“Specified key was too long”问题。

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

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

    或者你可以edit your MySQL configuration to remove this restriction 但不能没有一些django hackery

    更新:我刚刚再次遇到此问题并最终switching to PostgreSQL,因为我无法将VARCHAR缩减为191个字符。

答案 2 :(得分:67)

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

答案 3 :(得分:20)

如果它是一个新项目,我只需删除数据库,并使用适当的字符集创建一个新数据库:

CREATE DATABASE <dbname> CHARACTER SET utf8;

答案 4 :(得分: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表中并在渲染到模板以进行显示之前对其进行解码的唯一方法吗?

答案 5 :(得分:6)

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

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

答案 6 :(得分:1)

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

user.last_name = u'Slatkevičius'

或(当你没有字符串文字时)使用utf-8编码解码它们:

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

答案 7 :(得分:0)

只需更改您的表,无需任何操作。只需在数据库上运行此查询。 ALTER TABLE table_name转换为字符集utf8

肯定会工作。

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

希望这对除我以外的人都有帮助:)