165 votes

MySQL "valeur de chaîne incorrecte" erreur lors de l'enregistrement d'une chaîne unicode dans Django

J'ai reçu un message d'erreur étrange lorsque j'ai essayé de sauvegarder les prénoms et noms de famille dans le modèle auth_user de Django.

Exemples d'échec

user = User.object.create_user(username, email, password)
user.first_name = u'Rytis'
user.last_name = u'Slatkeviius'
user.save()
>>> Valeur de chaîne incorrecte : '\xC4\x8Dius' pour la colonne 'last_name' à la ligne 104

user.first_name = u''
user.last_name = u''
user.save()
>>> Valeur de chaîne incorrecte : '\xD0\x92\xD0\xB0\xD0\xBB...' pour la colonne 'first_name' à la ligne 104

user.first_name = u'Krzysztof'
user.last_name = u'Szukieoj'
user.save()
>>> Valeur de chaîne incorrecte : '\xC5\x82oj\xC4\x87' pour la colonne 'last_name' à la ligne 104

Exemples réussis

user.first_name = u'Marcin'
user.last_name = u'Król'
user.save()
>>> RÉUSSI

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 lignes dans l'ensemble (0.00 sec)

Charset et collation de la table

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

Résultats de la commande UPDATE

Aucune erreur n'a été signalée lors de la mise à jour des valeurs ci-dessus dans la table auth_user en utilisant la commande UPDATE.

mysql> update auth_user set last_name='Slatkeviiusa' where id=1;
Query OK, 1 lignes affectées, 1 avertissement (0.00 sec)
Lignes correspondantes : 1  Modifiées : 1  Avertissements : 0

mysql> select last_name from auth_user where id=100;
+---------------+
| last_name     |
+---------------+
| Slatkevi?iusa | 
+---------------+
1 ligne dans l'ensemble (0.00 sec)

PostgreSQL

Les valeurs échouées listées ci-dessus peuvent être mises à jour dans la table PostgreSQL lorsque j'ai changé 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 à partir de http://www.postgresql.org/docs/8.1/interactive/multibyte.html, j'ai trouvé ce qui suit :

Nom Octets/Car
UTF8 1-4

Cela signifie-t-il que les caractères unicode ont une longueur maximale de 4 octets dans PostgreSQL mais de 3 octets dans MySQL, ce qui a provoqué l'erreur ci-dessus ?

2 votes

C'est un problème de MySQL, pas de Django : stackoverflow.com/questions/1168036/…

160voto

donturner Points 3552

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

Vous ne pouvez pas stocker des caractères de 4 octets dans MySQL avec l'ensemble de caractères utf-8.

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

Pour résoudre cela, vous devez :

  1. Changer votre base de données MySQL, vos tables et colonnes pour utiliser l'ensemble de caractères utf8mb4 (disponible uniquement à partir de MySQL 5.5)
  2. Spécifiez le jeu de caractères dans votre fichier de configuration Django comme ci-dessous :

settings.py

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

Remarque : Lors de la recréation de votre base de données, vous pouvez rencontrer le problème 'Clé spécifiée était trop longue'.

La cause la plus probable est un CharField qui a une longueur maximale de 255 et un certain type d'index dessus (par exemple unique). Parce que utf8mb4 utilise 33% d'espace en plus que utf-8, vous devrez rendre ces champs 33% plus petits.

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

Alternativement, vous pouvez éditer votre configuration MySQL pour supprimer cette restriction mais pas sans un peu de bidouillage django

MISE À JOUR: Je suis tombé à nouveau sur ce problème et j'ai fini par passer à PostgreSQL car je n'ai pas réussi à réduire mon VARCHAR à 191 caractères.

13 votes

Cet réponse a besoin de beaucoup, beaucoup plus de votes. Merci! Le vrai problème est que votre application peut fonctionner correctement pendant des années jusqu'à ce que quelqu'un essaie d'entrer un caractère sur 4 octets.

3 votes

Ceci est absolument la bonne réponse. Le réglage OPTIONS est essentiel pour que django décoder les caractères d'emoji et les stocker dans MySQL. Changer simplement le jeu de caractères mysql en utf8mb4 via des commandes SQL n'est pas suffisant!

0 votes

Il n'est pas nécessaire de mettre à jour le jeu de caractères de l'ensemble de la table en utf8mb4. Mettez simplement à jour le jeu de caractères des colonnes nécessaires. De plus, l'option 'charset': 'utf8mb4' dans les paramètres de Django est critique, comme l'a dit @Xerion. Enfin, le problème d'index est un vrai casse-tête. Supprimez l'index sur la colonne, ou limitez sa longueur à 191 caractères, ou utilisez un TextField à la place!

124voto

user27478 Points 3660

J'ai eu le même problème et l'ai résolu en changeant l'ensemble de caractères de la colonne. Même si votre base de données a un ensemble de caractères par défaut de utf-8, je pense qu'il est possible que les colonnes de la base de données aient un ensemble 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;

14 votes

Ugh, j'ai changé tous les ensembles de caractères sur tout ce que je pouvais jusqu'à ce que je relise vraiment cette réponse : les colonnes peuvent avoir leurs propres ensembles de caractères, indépendants des tables et de la base de données. C'est fou et c'était exactement mon problème.

1 votes

Cela a également fonctionné pour moi, en utilisant mysql avec les valeurs par défaut, dans un modèle de champ de texte.

0 votes

Cela a résolu mon problème. La seule modification que j'ai faite a été d'utiliser utf8mb4 et utf8mb4_general_ci au lieu de utf8 / utf8_general_ci.

70voto

madprops Points 729

Si vous rencontrez ce problème, voici un script python pour modifier automatiquement toutes les colonnes de votre base de données mysql.

#! /usr/bin/env python
import MySQLdb

host = "localhost"
passwd = "passwd"
user = "votreutilisateur"
dbname = "votrenomdb"

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 votes

Cette solution a résolu tous mes problèmes avec une application django qui stockait des chemins de fichiers et de répertoires. Ajoutez dbname en tant que base de données django et laissez-la tourner. Ça a fonctionné à merveille!

1 votes

Ce code n'a pas fonctionné pour moi jusqu'à ce que j'ajoute db.commit() avant db.close().

1 votes

Est-ce que cette solution évite le problème discuté dans le commentaire de @markpasc : '...caractères UTF-8 de 4 octets tels que des emoji dans l'ensemble de caractères utf8 de 3 octets de MySQL 5.1' ?

8voto

jack Points 3198

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()
>>> RÉUSSI

print user.last_name
>>> Slatkevi\u010dius
print user.last_name.decode('unicode_escape')
>>> Slatkevičius

Est-ce la seule méthode pour enregistrer des chaînes de caractères de cette manière dans une table MySQL et les décoder avant de les afficher dans les templates ?

12 votes

J'ai un problème similaire, mais je ne suis pas d'accord que ce soit une solution valide. Lorsque vous .encode('unicode_escape'), vous ne stockez pas réellement des caractères unicode dans la base de données. Vous forcez tous les clients à les désencoder avant de les utiliser, ce qui signifie que cela ne fonctionnera pas correctement avec django.admin ou toutes sortes d'autres choses.

3 votes

Bien qu'il semble peu plaisant de stocker des codes d'échappement au lieu de caractères, il s'agit probablement de l'une des rares façons d'enregistrer des caractères UTF-8 de 4 octets tels que les emoji dans l'ensemble de caractères utf8 de 3 octets de MySQL 5.1.

2 votes

Il existe un encodage appelé utf8mb4 qui permet de stocker plus que le Plan Multilingue de Base. Je sais, vous penseriez que "UTF8" est tout ce dont vous avez besoin pour stocker pleinement Unicode. Eh bien, vous savez quoi, ce n'est pas le cas. Voir dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html

6voto

Wei An Points 414

Vous pouvez changer la collation de votre champ de texte en UTF8_general_ci et le problème sera résolu.

Remarque, cela ne peut pas être fait dans Django.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X