web-dev-qa-db-fra.com

Erreur MySQL "valeur de chaîne incorrecte" lors de l'enregistrement de la chaîne Unicode dans Django

J'ai reçu un message d'erreur étrange lorsque j'ai essayé de sauvegarder prénom_nom, nom_dernier sur le modèle auth_user de Django.

Exemples échoués

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

Exemples réussis

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

Paramètres 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)

Jeu de caractères et classement

La table auth_user a un jeu de caractères utf-8 avec un classement utf8_general_ci.

Résultats de la commande UPDATE

Il n'a généré aucune erreur lors de la mise à jour des valeurs précédentes dans la table auth_user à l'aide de la commande UPDATE.

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

Les valeurs d'échec répertoriées ci-dessus peuvent être mises à jour dans la table PostgreSQL lorsque j'ai basculé le backend de la base de données dans Django. C'est étrange.

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

Mais de http://www.postgresql.org/docs/8.1/interactive/multibyte.html , j'ai trouvé ce qui suit:

Name Bytes/Char
UTF8 1-4

Est-ce que cela signifie que le caractère unicode a un maximum de 4 octets dans PostgreSQL mais de 3 octets dans MySQL, ce qui a causé l'erreur ci-dessus?

141
jack

Je viens de trouver une méthode pour éviter les erreurs ci-dessus.

Enregistrer dans la base de données

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

Est-ce la seule méthode pour enregistrer de telles chaînes dans une table MySQL et les décoder avant de les restituer aux modèles à afficher?

8
jack

J'ai eu le même problème et je l'ai résolu en modifiant le jeu de caractères de la colonne. Même si votre base de données a un jeu de caractères par défaut de utf-8, il est possible que les colonnes de base de données aient un jeu de caractères différent dans MySQL. Voici la requête SQL que j'ai utilisée:

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

Aucune de ces réponses n'a résolu le problème pour moi. La cause fondamentale étant: 

Vous ne pouvez pas stocker des caractères de 4 octets dans MySQL avec le jeu de caractères utf-8.

MySQL a une limite de 3 octets sur les caractères utf-8 (oui, c'est wack, bien résumé par un développeur de Django ici )

Pour résoudre ce problème, vous devez: 

  1. Modifiez votre base de données, table et colonnes MySQL pour utiliser le jeu de caractères utf8mb4 (uniquement disponible à partir de MySQL 5.5)
  2. Spécifiez le jeu de caractères dans votre fichier de paramètres Django comme ci-dessous: 

settings.py

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

Remarque: Lorsque vous recréez votre base de données, vous pouvez rencontrer le problème ' La clé spécifiée était trop long '. 

La cause la plus probable est une CharField qui a une longueur maximale de 255 et une sorte d’index dessus (par exemple, unique). Comme utf8mb4 utilise 33% plus d’espace que utf-8, vous aurez besoin de réduire ces champs de 33%. 

Dans ce cas, modifiez la longueur maximale de 255 à 191. 

Sinon, vous pouvez éditer votre configuration MySQL pour supprimer cette restrictionmais pas sans Django hackery

UPDATE: Je viens juste de rencontrer à nouveau ce problème et je me suis retrouvé en passant à PostgreSQL car je ne pouvais pas réduire ma VARCHAR à 191 caractères.

106
donturner

Si vous avez ce problème, voici un script python pour changer automatiquement toutes les colonnes de votre base de données 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()
65
madprops

S'il s'agit d'un nouveau projet, il suffit de supprimer la base de données et d'en créer un nouveau avec un jeu de caractères approprié:

CREATE DATABASE <dbname> CHARACTER SET utf8;
20
Vanuan

Vous pouvez modifier le classement de votre champ de texte en UTF8_general_ci et le problème sera résolu.

Notez que cela ne peut pas être fait dans Django.

6
Wei An

Vous n'essayez pas de sauvegarder des chaînes unicode, vous essayez de sauvegarder des chaînes de caractères dans le codage UTF-8. Faites-en de vrais littéraux de chaînes unicode:

user.last_name = u'Slatkevičius'

ou (lorsque vous n'avez pas de littéraux de chaîne), décodez-les en utilisant le codage utf-8:

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

Modifiez simplement votre table, pas besoin de rien. Il suffit d’exécuter cette requête sur la base de données .ALTER TABLE table_name CONVERT TO CHARACTER SET utf8

ça va définitivement marcher.

0
Rishabh Jhalani