Помилка "неправильного значення рядка" MySQL при збереженні рядка Unicode в Django


158

Я отримав дивне повідомлення про помилку, коли намагався зберегти ім’я, прізвище до моделі auth_user Django.

Невдалі приклади

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

Під час оновлення вищевказаних значень до таблиці auth_user за допомогою команди 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

Невдалі значення, перелічені вище, можна оновити в таблицю PostgreSQL, коли я переключив резервний сервер бази даних у Django. Це дивно.

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 має максимум 4 байти в PostgreSQL, але 3 байти в MySQL, що спричинило вище помилку?


2
Це проблема , MySQL, а НЕ Django: stackoverflow.com/questions/1168036 / ...
Vanuan

Відповіді:


140

Жодна з цих відповідей не вирішила проблему для мене. Першопричиною є:

Не можна зберігати 4-байтні символи в MySQL із набором символів utf-8.

У MySQL є обмеження на 3 байти щодо символів utf-8 (так, це епізод, добре підведений тут розробником Django )

Для вирішення цього питання потрібно:

  1. Змініть базу даних, таблицю та стовпці MySQL, щоб використовувати набір символів utf8mb4 (доступний лише від MySQL 5.5 далі)
  2. Вкажіть діаграму у файлі налаштувань Django, як показано нижче:

settings.py

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

Примітка. Під час відтворення вашої бази даних ви можете зіткнутися з проблемою " Зазначений ключ був занадто довгим ".

Найбільш вірогідною причиною є a, CharFieldщо має максимальну довжину 255 і якийсь індекс на ньому (наприклад, унікальний). Оскільки utf8mb4 використовує на 33% більше місця, ніж utf-8, вам потрібно зробити ці поля на 33% меншими.

У цьому випадку змініть максимальну довжину з 255 на 191.

Крім того, ви можете відредагувати конфігурацію MySQL, щоб видалити це обмеження, але не позбавившись певного хакеру django

ОНОВЛЕННЯ: Я просто знову зіткнувся з цією проблемою і перейшов на PostgreSQL, оскільки мені не вдалося скоротити VARCHARдо 191 символу.


13
ця відповідь потребує способів, шляхів, способів більшої кількості відгуків. Дякую! Справжня проблема полягає в тому, що ваша програма може працювати нормально протягом багатьох років, поки хтось не спробує ввести 4-байтний символ.
Майкл Більстра

2
Це абсолютно правильна відповідь. Налаштування OPTIONS є критичним для того, щоб створити символи джанго для декодування емоджи-символів та зберегти їх у MySQL. Просто зміни mysql-діаграми на utf8mb4 за допомогою команд SQL недостатньо!
Ксеріон

Немає необхідності оновлювати набір символів для всієї таблиці до utf8mb4. Просто оновіть набір символів необхідних стовпців. Також 'charset': 'utf8mb4'параметр у налаштуваннях Джанго є критичним, як сказав @Xerion. Нарешті, проблема з індексом - безлад. Видаліть індекс на стовпчику або зробіть його довжину не більше 191 або використовуйте TextFieldзамість цього!
Rockallite

2
Мені подобається ваше посилання на цю цитату: Це лише черговий випадок, коли MySQL цілеспрямовано і незворотно пошкодив мозок. :)
Qback

120

У мене була така ж проблема і була вирішена, змінивши набір символів стовпця. Незважаючи на те, що у вашій базі даних є набір символів за замовчуванням, utf-8я думаю, що для колонок бази даних можливий інший набір символів у MySQL. Ось я використовував запит SQL:

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

14
Фу, я змінив усі набори символів на все, що міг, поки я справді не перечитав цю відповідь: стовпці можуть мати власні набори символів, незалежні від таблиць та бази даних. Це божевільно і теж було саме моєю проблемою.
markpasc

1
Це працювало і для мене, використовуючи mysql із типовими настройками, у моделі TextField.
madprops

Це вирішило мою проблему. Єдиною зміною, яку я зробив, було використання utf8mb4 та utf8mb4_general_ci замість utf8 / utf8_general_ci.
Michal Przysucha

70

Якщо у вас є ця проблема, ось сценарій 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()

4
Це рішення вирішило всі мої проблеми за допомогою програми django, яка зберігала шляхи до файлів і каталогів. Киньте dbname як вашу базу даних django і нехай вона працює. Працював як шарм!
Кріс

1
Цей код не працював для мене, поки я не додав його db.commit()раніше db.close().
Марк Ердман

1
Чи не вдасться це рішення уникнути проблеми, обговорюваної в коментарі @markpasc: "... 4-байтні символи UTF-8, такі як емоджи в 3-
байтному

рішення допоможіть мені, коли я видаляв запис через адміністратора django, у мене не було проблем при створенні редагування o ... дивно! Мені навіть вдалося видалити безпосередньо в db
Хав'єр Вієйра

Чи повинен це робити кожен раз, коли я міняю Модель?
Вануан

25

Якщо це новий проект, я просто скину базу даних і створіть нову з належним шаблоном:

CREATE DATABASE <dbname> CHARACTER SET utf8;

Привіт, ласкаво допоможіть перевірити це питання stackoverflow.com/questions/46348817/…
Король

У моєму випадку наш db створений docker, щоб виправити, я додав у db: command: інструкція у моєму файлі compose:- --character-set-server=utf8
followben

1
Так просто. Дякуємо @Vanuan
Enku

якщо це не новий проект, ми отримуємо резервну копію з db, скидаємо його і відтворюємо його за допомогою charf utf8, а потім відновлюємо резервне копіювання. Я зробив це у своєму проекті, який не був новим ...
Мохаммед Реза

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 та декодування їх перед рендерінгом у шаблони для відображення?


12
У мене є аналогічна проблема, але я не згоден, що це правильне рішення. Коли ви .encode('unicode_escape')насправді не зберігаєте символи Unicode в базі даних. Ви змушуєте всіх клієнтів скасовувати код перед їх використанням, а це означає, що він не працюватиме належним чином з django.admin або всілякими іншими речами.
мудскоп

3
Хоча здається, що зберігати коди евакуації замість символів неприємно, мабуть, це один із небагатьох способів збереження 4-байтних символів UTF-8, таких як емоджи в 3-байтовому utf8наборі символів MySQL 5.1 .
markpasc

2
Існує кодування, яке називається, utf8mb4що дозволяє зберігати більше, ніж Основна багатомовна площина. Я знаю, ви можете подумати, що "UTF8" - це все, що потрібно для повного зберігання Unicode. Що ж, знаю, це не так. Дивіться dev.mysql.com/doc/refman/5.5/uk/charset-unicode-utf8mb4.html
Mihai Danila

@jack ви можете подумати про зміну прийнятої відповіді на більш корисну
donturner

це можливе вирішення, але я також не рекомендую його використовувати (як закликає @muudscope). Я все ще не можу зберігати, наприклад, емоджи в базах даних mysql. Хтось це здійснив?
Marcelo Sardelich

6

Ви можете змінити порівняння свого текстового поля на UTF8_general_ci, і проблема буде вирішена.

Зауважте, цього не можна зробити в Джанго.


1

Ви не намагаєтеся зберегти рядки Unicode, ви намагаєтеся зберегти bytestrings в кодуванні UTF-8. Зробіть їх фактичними лінійними буквами Unicode:

user.last_name = u'Slatkevičius'

або (якщо у вас немає рядкових літералів) розшифруйте їх за допомогою кодування utf-8:

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

@Thomas, я спробував саме так, як ви сказали, але все одно виникають ті ж помилки.
джек

0

Просто переробляйте свій стіл, не потрібно нічого. просто запустіть цей запит у базі даних. ПОВЕРНЕННЯ ТАБЛИЧНОГО table_nameПОВЕРНЕННЯ ДО НАСТРОЮ ХАРАКТЕРУ utf8

це безумовно спрацює.


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

Сподіваюся, це допомагає комусь, крім мене :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.