Kesalahan "nilai string salah" MySQL saat menyimpan string unicode di Django

158

Saya mendapat pesan kesalahan aneh ketika mencoba menyimpan first_name, last_name ke model auth_user Django.

Contoh gagal

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

Contoh sukses

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

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

Tabel charset dan collation

Tabel auth_user memiliki charset utf-8 dengan collation utf8_general_ci.

Hasil dari perintah UPDATE

Itu tidak menimbulkan kesalahan ketika memperbarui nilai-nilai di atas ke tabel auth_user dengan menggunakan perintah 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

Nilai-nilai gagal yang tercantum di atas dapat diperbarui ke tabel PostgreSQL ketika saya mengganti database backend di Django. Ini aneh.

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

Tetapi dari http://www.postgresql.org/docs/8.1/interactive/multibyte.html , saya menemukan yang berikut:

Name Bytes/Char
UTF8 1-4

Apakah ini berarti unicode char memiliki maksimal 4 byte di PostgreSQL tetapi 3 byte di MySQL yang menyebabkan kesalahan di atas?

mendongkrak
sumber
2
Ini masalah MySQL, bukan Django: stackoverflow.com/questions/1168036/…
Vanuan

Jawaban:

140

Tak satu pun dari jawaban ini memecahkan masalah bagi saya. Penyebab utamanya adalah:

Anda tidak dapat menyimpan karakter 4-byte di MySQL dengan set karakter utf-8.

MySQL memiliki batas 3 byte pada utf-8 karakter (ya, itu aneh, disimpulkan dengan baik oleh pengembang Django di sini )

Untuk mengatasi ini, Anda perlu:

  1. Ubah database, tabel, dan kolom MySQL Anda untuk menggunakan set karakter utf8mb4 (hanya tersedia dari MySQL 5.5 dan seterusnya)
  2. Tentukan rangkaian karakter dalam file pengaturan Django Anda seperti di bawah ini:

settings.py

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

Catatan: Saat membuat ulang basis data Anda, Anda mungkin mengalami masalah ' Kunci spesifik terlalu panjang '.

Penyebab paling mungkin adalah CharFieldyang memiliki max_length 255 dan beberapa jenis indeks di atasnya (misalnya unik). Karena utf8mb4 menggunakan 33% lebih banyak ruang daripada utf-8 Anda harus membuat bidang ini 33% lebih kecil.

Dalam hal ini, ubah max_length dari 255 menjadi 191.

Atau Anda dapat mengedit konfigurasi MySQL Anda untuk menghapus pembatasan ini tetapi tidak tanpa beberapa hackery Django

UPDATE: Saya baru saja mengalami masalah ini lagi dan akhirnya beralih ke PostgreSQL karena saya tidak dapat mengurangi saya VARCHARmenjadi 191 karakter.

pelanggar
sumber
13
jawaban ini membutuhkan cara, cara, cara lebih banyak upvotes. Terima kasih! Masalah sebenarnya adalah aplikasi Anda dapat berjalan dengan baik selama bertahun-tahun sampai seseorang mencoba memasukkan karakter 4byte.
Michael Bylstra
2
Ini benar-benar jawaban yang tepat. Pengaturan OPSI sangat penting untuk membuat karakter emoji deco django dan menyimpannya di MySQL. Hanya mengubah charset mysql ke utf8mb4 melalui perintah SQL tidak cukup!
Xerion
Tidak perlu memperbarui set karakter seluruh tabel ke utf8mb4. Perbarui set karakter kolom yang diperlukan. Juga 'charset': 'utf8mb4'opsi dalam pengaturan Django sangat penting, seperti yang dikatakan @Xerion. Akhirnya, masalah indeks berantakan. Hapus indeks pada kolom, atau buat panjangnya tidak lebih dari 191, atau gunakan a TextField!
Rockallite
2
Saya suka tautan Anda ke kutipan ini: Ini hanyalah kasus lain dari MySQL yang sengaja dan ireversibel rusak otak. :)
Qback
120

Saya memiliki masalah yang sama dan mengatasinya dengan mengubah set karakter kolom. Meskipun database Anda memiliki set karakter default, utf-8saya pikir itu mungkin untuk kolom database memiliki set karakter yang berbeda di MySQL. Inilah SQL QUERY yang saya gunakan:

    ALTER TABLE database.table MODIFY COLUMN col VARCHAR(255)
    CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
gerdemb
sumber
14
Ugh, saya mengubah semua set karakter pada semua yang saya bisa sampai saya benar-benar membaca kembali jawaban ini: kolom dapat memiliki set karakter sendiri , terlepas dari tabel dan database. Itu gila dan juga persis masalah saya.
markpasc
1
Ini bekerja untuk saya juga, menggunakan mysql dengan default, dalam model TextField.
madprops
Ini menyelesaikan masalah saya. Satu-satunya perubahan yang saya lakukan adalah menggunakan utf8mb4 dan utf8mb4_general_ci alih-alih utf8 / utf8_general_ci.
Michal Przysucha
70

Jika Anda memiliki masalah ini, inilah skrip python untuk mengubah semua kolom basis data mysql Anda secara otomatis.

#! /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()
madprops
sumber
4
Solusi ini menyelesaikan semua masalah saya dengan aplikasi Django yang menyimpan path file dan direktori. Aduk dbname sebagai basis data Django Anda dan biarkan berjalan. Bekerja seperti pesona!
Chris
1
Kode ini tidak berfungsi untuk saya sampai saya tambahkan db.commit()sebelumnya db.close().
Mark Erdmann
1
Apakah solusi ini menghindari masalah yang dibahas dalam komentar @markpasc: '... 4-byte karakter UTF-8 seperti emoji di set karakter utf8 3-byte MySQL 5.1'
CatShoes
solusinya bantu saya ketika saya menghapus catatan melalui admin django, saya tidak punya masalah saat membuat o editing ... aneh! Saya bahkan dapat menghapus langsung di db
Javier Vieira
Haruskah saya melakukan ini setiap kali saya mengubah Model?
Vanuan
25

Jika ini adalah proyek baru, saya hanya akan membuang database, dan membuat yang baru dengan charset yang tepat:

CREATE DATABASE <dbname> CHARACTER SET utf8;
Vanuan
sumber
Hai, tolong bantu periksa pertanyaan ini stackoverflow.com/questions/46348817/…
King
Dalam kasus saya, db kami dibuat oleh buruh pelabuhan sehingga untuk memperbaikinya saya menambahkan yang berikut ke db: perintah: instruksi dalam file penulisan saya:- --character-set-server=utf8
followben
1
Sesimpel itu. Terima kasih @Vanuan
Enku
jika ini bukan proyek baru, kita mendapatkan cadangan dari db, letakkan dan buat ulang dengan utf8 charset lalu kembalikan cadangan. Saya melakukannya dalam proyek saya yang bukan baru ...
Mohammad Reza
8

Saya baru saja menemukan satu metode untuk menghindari kesalahan di atas.

Simpan ke basis data

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

Apakah ini satu-satunya metode untuk menyimpan string seperti itu ke dalam tabel MySQL dan mendekodekannya sebelum dirender ke templat untuk ditampilkan?

mendongkrak
sumber
12
Saya mengalami masalah yang sama, tetapi saya tidak setuju bahwa ini adalah solusi yang valid. Saat Anda .encode('unicode_escape')sebenarnya tidak menyimpan karakter unicode dalam database. Anda memaksa semua klien untuk membuka kode sebelum menggunakannya, yang berarti itu tidak akan berfungsi dengan baik dengan django.admin atau segala hal lainnya.
muudscope
3
Walaupun rasanya tidak menyenangkan untuk menyimpan kode pelarian alih-alih karakter, ini mungkin salah satu dari sedikit cara untuk menyimpan karakter UTF-8 4-byte seperti emoji di utf8set karakter 3-byte MySQL 5.1 .
markpasc
2
Ada pengkodean yang disebut utf8mb4yang memungkinkan lebih dari Basic Multilingual Plane untuk disimpan. Saya tahu, Anda akan berpikir "UTF8" adalah semua yang diperlukan untuk menyimpan Unicode sepenuhnya. Nah, whaddaya tahu, bukan. Lihat dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
Mihai Danila
@Jack, Anda mungkin ingin mempertimbangkan untuk mengubah jawaban yang diterima menjadi yang lebih berguna
donturner
ini adalah solusi yang layak, tetapi saya tidak merekomendasikan menggunakannya juga (seperti yang disarankan oleh @muudscope). Saya masih tidak dapat menyimpan, misalnya, emoji ke database mysql. Adakah yang berhasil?
Marcelo Sardelich
6

Anda dapat mengubah susunan bidang teks Anda menjadi UTF8_general_ci dan masalah akan terpecahkan.

Perhatikan, ini tidak dapat dilakukan di Django.

Wei An
sumber
1

Anda tidak mencoba menyimpan string unicode, Anda mencoba menyimpan bytestrings dalam pengkodean UTF-8. Jadikan mereka literal string unicode yang sebenarnya:

user.last_name = u'Slatkevičius'

atau (ketika Anda tidak memiliki string literal) mendekode mereka menggunakan pengkodean utf-8:

user.last_name = lastname.decode('utf-8')
Thomas Wouters
sumber
@ Thomas, saya mencoba persis seperti yang Anda katakan tetapi masih memunculkan kesalahan yang sama.
jack
0

Cukup ubah meja Anda, tidak perlu apa-apa. jalankan saja query ini pada basis data. ALTER TABLE table_nameCONVERT KE SET KARAKTER utf8

itu pasti akan berhasil.

Rishabh Jhalani
sumber
0

Peningkatan jawaban @madprops - solusi sebagai perintah manajemen 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()

Semoga ini bisa membantu siapa pun kecuali saya :)

Ron
sumber