Error de MySQL "valor de cadena incorrecto" al guardar cadena unicode en Django


Recibí un mensaje de error extraño cuando intenté guardar first_name, last_name en el modelo auth_user de Django.

Ejemplos fallidos

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

Ejemplos de éxito

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

Configuración de 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)

Conjunto de caracteres y cotejo de la tabla

La tabla auth_user tiene un conjunto de caracteres utf-8 con intercalación utf8_general_ci.

Resultados del comando de actualización

No generó ningún error al actualizar los valores anteriores a auth_user tabla mediante el comando 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

Los valores fallidos listados arriba se pueden actualizar a la tabla PostgreSQL cuando cambié el backend de la base de datos en Django. Es extraño.

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

Pero desde http://www.postgresql.org/docs/8.1/interactive/multibyte.html , encontré lo siguiente:

Name Bytes/Char
UTF8 1-4

¿Significa que unicode char tiene maxlen de 4 bytes en PostgreSQL pero 3 bytes en MySQL que causaron el error anterior?

Author: Alan Moore, 2010-01-21

8 answers

Tuve el mismo problema y lo resolví cambiando el conjunto de caracteres de la columna. A pesar de que su base de datos tiene un conjunto de caracteres predeterminado de utf-8 Creo que es posible que las columnas de la base de datos tengan un conjunto de caracteres diferente en MySQL. Aquí está la CONSULTA SQL que usé:

    ALTER TABLE database.table MODIFY COLUMN col VARCHAR(255)
    CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
 108
Author: user27478,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-04 14:59:08

Ninguna de estas respuestas resolvió el problema para mí. La causa raíz es:

No puede almacenar caracteres de 4 bytes en MySQL con el conjunto de caracteres utf-8.

MySQL tiene un límite de 3 bytes en caracteres utf-8 (sí, es wack, muy bien resumido por un desarrollador de Django aquí)

Para resolver esto necesitas:

  1. Cambie su base de datos MySQL, tabla y columnas para usar el conjunto de caracteres utf8mb4 (solo disponible desde MySQL 5.5 en adelante)
  2. Especifique el conjunto de caracteres en su archivo de configuración de Django de la siguiente manera:

Settings.py

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

Nota: Al recrear su base de datos, puede encontrarse con el problema 'La clave especificada era demasiado larga'.

La causa más probable es un CharField que tiene un max_length de 255 y algún tipo de índice en él (por ejemplo, único). Debido a que utf8mb4 usa un 33% más de espacio que utf-8, necesitará hacer que estos campos sean un 33% más pequeños.

En este caso, cambie la max_length de 255 a 191.

Alternativamente puede editar su configuración de MySQL para eliminar esta restricción pero no sin algo de hacker django

ACTUALIZACIÓN: Me encontré con este problema de nuevo y terminé cambiando a PostgreSQL porque no pude reducir mi VARCHAR a 191 caracteres.

 98
Author: donturner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-30 21:07:56

Si tiene este problema, aquí hay un script python para cambiar todas las columnas de su base de datos mysql automáticamente.

#! /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()
 64
Author: madprops,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-05 22:35:28

Si se trata de un proyecto nuevo, simplemente soltaría la base de datos y crearía uno nuevo con un conjunto de caracteres adecuado:

CREATE DATABASE <dbname> CHARACTER SET utf8;
 17
Author: Vanuan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-21 19:57:05

Acabo de descubrir un método para evitar los errores anteriores.

Guardar en la base de datos

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

¿Es este el único método para guardar cadenas como esa en una tabla MySQL y decodificarla antes de renderizarla en plantillas para su visualización?

 6
Author: jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-21 15:29:09

Puede cambiar la intercalación de su campo de texto a UTF8_general_ci y el problema se resolverá.

Tenga en cuenta que esto no se puede hacer en Django.

 6
Author: Wei An,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-02 15:54:06

No está tratando de guardar cadenas unicode, está tratando de guardar bytestrings en la codificación UTF-8. Convertirlos en literales de cadena unicode reales:

user.last_name = u'Slatkevičius'

O (cuando no tiene literales de cadena) descodifíquelos usando la codificación utf-8:

user.last_name = lastname.decode('utf-8')
 1
Author: Thomas Wouters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-21 11:44:50

Simplemente altere su mesa, sin necesidad de nada. simplemente ejecute esta consulta en la base de datos. ALTER TABLE table_nameCONVERTIR A CONJUNTO DE CARACTERES utf8

Definitivamente funcionará.

 0
Author: Rishabh Jhalani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-13 12:41:40