ข้อผิดพลาด“ ค่าสตริงไม่ถูกต้อง” MySQL เมื่อบันทึกสตริงยูนิโค้ดใน Django


158

ฉันได้รับข้อความแสดงข้อผิดพลาดแปลก ๆ เมื่อพยายามบันทึก first_name, last_name ไปยังโมเดล 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 ถ่านมี maxlen จาก 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'},
    }
}

หมายเหตุ: เมื่อสร้างฐานข้อมูลของคุณใหม่คุณอาจพบปัญหา ' ระบุคีย์ยาวเกินไป '

สาเหตุที่เป็นไปได้มากที่สุดคือสาเหตุCharFieldที่มีความยาวสูงสุด 255 และดัชนีบางชนิด (เช่นไม่ซ้ำกัน) เนื่องจาก utf8mb4 ใช้พื้นที่ว่างมากกว่า utf-8 33% คุณจะต้องทำให้ฟิลด์เหล่านี้เล็กลง 33%

ในกรณีนี้เปลี่ยน max_length จาก 255 เป็น 191

อีกวิธีหนึ่งคุณสามารถแก้ไขการกำหนดค่า MySQL ของคุณเพื่อลบข้อ จำกัด นี้ ได้

อัปเดต:ฉันเพิ่งพบปัญหานี้อีกครั้งและจบลงที่การเปลี่ยนเป็น PostgreSQLเพราะฉันไม่สามารถลดจำนวนVARCHARตัวอักษรลงเหลือ 191 ตัว


13
คำตอบนี้ต้องการวิธี, วิธี, วิธี upvotes มากขึ้น ขอบคุณ! ปัญหาที่แท้จริงคือแอปพลิเคชันของคุณอาจทำงานได้ดีเป็นเวลาหลายปีจนกระทั่งมีคนพยายามป้อนอักขระ 4byte
Michael Bylstra

2
นี่คือคำตอบที่ถูกต้องอย่างแน่นอน การตั้งค่าตัวเลือกมีความสำคัญอย่างยิ่งที่จะทำให้ django ถอดรหัสอักขระอิโมจิและเก็บไว้ใน MySQL เพียงแค่เปลี่ยนชุดอักขระ mysql เป็น utf8mb4 ผ่านคำสั่ง SQL ไม่เพียงพอ!
Xerion

ไม่จำเป็นต้องอัปเดตชุดอักขระของทั้งตารางเป็น utf8mb4 เพียงอัปเดตชุดอักขระของคอลัมน์ที่จำเป็น นอกจากนี้ยังมี'charset': 'utf8mb4'ตัวเลือกในการตั้งค่า Django เป็นสิ่งสำคัญเช่น @Xerion กล่าวว่า ในที่สุดปัญหาดัชนีเป็นระเบียบ ลบดัชนีในคอลัมน์หรือทำให้มีความยาวไม่เกิน 191 หรือใช้TextFieldแทน!
Rockallite

2
ฉันชอบที่ลิงค์ของคุณไปยังข้อความนี้: นี่เป็นอีกกรณีหนึ่งของ MySQL ที่มีจุดประสงค์และทำให้สมองเสียหาย :)
Qback

120

ฉันมีปัญหาเดียวกันและแก้ไขมันโดยการเปลี่ยนชุดอักขระของคอลัมน์ แม้ว่าฐานข้อมูลของคุณจะมีชุดอักขระเริ่มต้นของutf-8ฉันคิดว่ามันเป็นไปได้สำหรับคอลัมน์ฐานข้อมูลที่จะมีชุดอักขระที่แตกต่างกันใน MySQL นี่คือ SQL QUERY ที่ฉันใช้:

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

1
โซลูชันนี้หลีกเลี่ยงปัญหาที่กล่าวถึงในความคิดเห็น @markpasc: '... อักขระ UTF-8 4 ไบต์เช่นอีโมจิในชุดอักขระ 3 ไบต์ utf8 ขนาด 3 ไบต์ไบต์ของ MySQL 5.1
CatShoes

วิธีการแก้ปัญหาช่วยฉันเมื่อฉันลบบันทึกราง django admin ฉันไม่ได้มีปัญหาใด ๆ เมื่อสร้าง o การแก้ไข ... แปลก! ฉันสามารถลบได้โดยตรงใน db
Javier Vieira

ฉันควรทำสิ่งนี้ทุกครั้งที่เปลี่ยนรุ่นหรือไม่
Vanuan

25

หากเป็นโครงการใหม่ฉันจะลบฐานข้อมูลและสร้างโครงการใหม่ด้วยชุดอักขระที่เหมาะสม:

CREATE DATABASE <dbname> CHARACTER SET utf8;

สวัสดีกรุณาช่วยตรวจสอบคำถามนี้stackoverflow.com/questions/46348817/…
King

ในกรณีของฉันฐานข้อมูลของเราถูกสร้างขึ้นโดยนักเทียบท่าเพื่อแก้ไขฉันเพิ่มต่อไปนี้ในคำสั่ง db: คำสั่งในไฟล์เขียนของฉัน:- --character-set-server=utf8
followben

1
ง่ายเหมือนที่ ขอบคุณ @Vanuan
Enku

หากนี่ไม่ใช่โครงการใหม่เราจะได้รับการสำรองข้อมูลจาก db, ปล่อยมันและสร้างใหม่ด้วย utf8 charset จากนั้นเรียกคืนการสำรองข้อมูล ฉันทำมันในโครงการของฉันที่ไม่ใช่เรื่องใหม่ ...
Mohammad Reza

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 หรือสิ่งอื่น ๆ ทุกประเภท
muudscope

3
ในขณะที่การเก็บรหัส Escape แทนตัวละครนั้นเป็นเรื่องที่น่ารังเกียจนี่อาจเป็นวิธีหนึ่งในไม่กี่วิธีในการบันทึกอักขระ UTF-8 ขนาด 4 ไบต์เช่นอีโมจิในutf8ชุดอักขระ3 ไบต์ของ MySQL 5.1
markpasc

2
มีการเข้ารหัสที่เรียกutf8mb4ว่าอนุญาตให้เก็บเครื่องบินแบบหลายภาษาพื้นฐานได้มากกว่า ฉันรู้ว่าคุณคิดว่า "UTF8" เป็นสิ่งที่จำเป็นสำหรับการจัดเก็บ Unicode อย่างสมบูรณ์ เอาละรู้แล้วใช่มั้ย ดูdev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
Mihai Danila

@jack คุณอาจต้องการพิจารณาเปลี่ยนคำตอบที่ยอมรับเป็นคำตอบที่มีประโยชน์มากขึ้น
donturn

มันเป็นวิธีแก้ปัญหาที่เป็นไปได้ แต่ฉันไม่แนะนำให้ใช้เช่นกัน (ตามที่สนับสนุนโดย @muudscope) ฉันยังไม่สามารถจัดเก็บตัวอย่างเช่น emoji ไปยังฐานข้อมูล mysql มีใครทำสำเร็จหรือไม่?
Marcelo Sardelich

6

คุณสามารถเปลี่ยนการเรียงฟิลด์ข้อความของคุณเป็น UTF8_general_ci และปัญหาจะได้รับการแก้ไข

สังเกตเห็นสิ่งนี้ไม่สามารถทำได้ใน Django


1

คุณไม่ได้พยายามที่จะบันทึกสตริง unicode คุณกำลังพยายามบันทึก bytestrings ในการเข้ารหัส UTF-8 ทำให้เป็นจริงตัวอักษรสตริง Unicode จริง:

user.last_name = u'Slatkevičius'

หรือ (เมื่อคุณไม่มีตัวอักษรสตริง) ถอดรหัสพวกเขาโดยใช้การเข้ารหัส utf-8:

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

@ โทมัสฉันลองทำตามที่คุณพูด แต่ก็ยังทำให้เกิดข้อผิดพลาดเหมือนเดิม
แจ็ค

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

หวังว่านี่จะช่วยใครก็ได้ แต่ฉัน :)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.