MySQL không đúng giá trị chuỗi Lỗi lỗi khi lưu chuỗi unicode trong Django


158

Tôi đã nhận được thông báo lỗi lạ khi cố lưu First_name, last_name vào mô hình auth_user của Django.

Ví dụ thất bại

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

Ví dụ thành công

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

Cài đặt 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)

Bảng ký tự và đối chiếu

Bảng auth_user có bộ ký tự utf-8 với đối chiếu utf8_general_ci.

Kết quả của lệnh CẬP NHẬT

Nó không tăng bất kỳ lỗi nào khi cập nhật các giá trị trên vào bảng auth_user bằng cách sử dụng lệnh 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

Các giá trị thất bại được liệt kê ở trên có thể được cập nhật vào bảng PostgreSQL khi tôi chuyển đổi phụ trợ cơ sở dữ liệu trong Django. Nó thật kì lạ.

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

Nhưng từ http://www.postgresql.org/docs/8.1/interactive/multibyte.html , tôi đã tìm thấy như sau:

Name Bytes/Char
UTF8 1-4

Có phải nó có nghĩa là unicode char có maxlen 4 byte trong PostgreSQL nhưng 3 byte trong MySQL đã gây ra lỗi trên?


2
Đó là sự cố của MySQL, không phải Django: stackoverflow.com/questions/1168036/ cấp
Vanuan

Câu trả lời:


139

Không có câu trả lời nào giải quyết được vấn đề cho tôi. Nguyên nhân sâu xa là:

Bạn không thể lưu trữ các ký tự 4 byte trong MySQL với bộ ký tự utf-8.

MySQL có giới hạn 3 byte đối với các ký tự utf-8 (vâng, đó là wack, được tóm tắt độc đáo bởi một nhà phát triển Django ở đây )

Để giải quyết điều này bạn cần phải:

  1. Thay đổi cơ sở dữ liệu, bảng và cột MySQL của bạn để sử dụng bộ ký tự utf8mb4 (chỉ có sẵn từ MySQL 5.5 trở đi)
  2. Chỉ định bộ ký tự trong tệp cài đặt Django của bạn như dưới đây:

cài đặt

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

Lưu ý: Khi tạo lại cơ sở dữ liệu của bạn, bạn có thể gặp phải sự cố ' Khóa được chỉ định quá dài '.

Nguyên nhân rất có thể là nguyên nhân CharFieldcó max_length là 255 và một số loại chỉ mục trên đó (ví dụ: duy nhất). Vì utf8mb4 sử dụng nhiều dung lượng hơn 33% so với utf-8, bạn sẽ cần làm cho các trường này nhỏ hơn 33%.

Trong trường hợp này, thay đổi max_length từ 255 thành 191.

Ngoài ra, bạn có thể chỉnh sửa cấu hình MySQL của mình để loại bỏ hạn chế này nhưng không phải không có một số hack django

CẬP NHẬT: Tôi vừa gặp vấn đề này một lần nữa và cuối cùng chuyển sang PostgreSQL vì tôi không thể giảm VARCHARxuống còn 191 ký tự.


13
Câu trả lời này cần cách, cách, cách nâng cao hơn. Cảm ơn! Vấn đề thực sự là ứng dụng của bạn có thể chạy tốt trong nhiều năm cho đến khi ai đó cố gắng nhập ký tự 4byte.
Michael Bylstra

2
Đây hoàn toàn là câu trả lời đúng. Cài đặt TÙY CHỌN là rất quan trọng để làm cho django giải mã các ký tự biểu tượng cảm xúc và lưu trữ chúng trong MySQL. Chỉ cần thay đổi bộ ký tự mysql thành utf8mb4 thông qua các lệnh SQL là không đủ!
Xerion

Không cần cập nhật bộ ký tự của toàn bộ bảng thành utf8mb4. Chỉ cần cập nhật bộ ký tự của các cột cần thiết. Ngoài ra, 'charset': 'utf8mb4'tùy chọn trong cài đặt Django rất quan trọng, như @Xerion nói. Cuối cùng, vấn đề chỉ số là một mớ hỗn độn. Xóa chỉ mục trên cột hoặc làm cho chiều dài của nó không quá 191 hoặc sử dụng TextFieldthay thế!
Rockallite

2
Tôi thích liên kết của bạn với trích dẫn này: Đây chỉ là một trường hợp khác của MySQL bị tổn thương não có chủ đích và không thể phục hồi. :)
Qback

120

Tôi đã có cùng một vấn đề và giải quyết nó bằng cách thay đổi bộ ký tự của cột. Mặc dù cơ sở dữ liệu của bạn có bộ ký tự mặc định, utf-8tôi nghĩ rằng các cột cơ sở dữ liệu có bộ ký tự khác trong MySQL. Đây là SQL QUERY tôi đã sử dụng:

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

14
Ugh, tôi đã thay đổi tất cả các bộ ký tự trên mọi thứ tôi có thể cho đến khi tôi thực sự đọc lại câu trả lời này: các cột có thể có các bộ ký tự riêng , độc lập với các bảng và cơ sở dữ liệu. Điều đó thật điên rồ và cũng chính xác là vấn đề của tôi.
markpasc

1
Điều này cũng làm việc với tôi, sử dụng mysql với các giá trị mặc định, trong mô hình TextField.
madprops

Điều này đã giải quyết vấn đề của tôi. Thay đổi duy nhất tôi đã làm là sử dụng utf8mb4 và utf8mb4_general_ci thay vì utf8 / utf8_general_ci.
Michal Przysucha

70

Nếu bạn gặp vấn đề này thì đây là tập lệnh python để tự động thay đổi tất cả các cột trong cơ sở dữ liệu mysql của bạn.

#! /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
Giải pháp này đã giải quyết tất cả các vấn đề của tôi với ứng dụng django đang lưu trữ đường dẫn tệp và thư mục. Sử dụng dbname làm cơ sở dữ liệu django của bạn và để nó chạy. Làm việc như người ở!
Chris

1
Mã này đã không làm việc cho tôi cho đến khi tôi thêm db.commit()trước đó db.close().
Mark Erdmann

1
Giải pháp này có tránh được vấn đề được thảo luận trong bình luận @markpasc: '... Các ký tự UTF-8 4 byte như biểu tượng cảm xúc trong bộ ký tự utf8 3 byte của MySQL 5.1'
CatShoes

giải pháp giúp tôi khi tôi xóa một quản trị viên django máng kỷ lục, tôi không gặp vấn đề gì khi tạo o chỉnh sửa ... kỳ lạ! Tôi thậm chí đã có thể xóa trực tiếp trong db
Javier Vieira

Tôi có nên làm điều này mỗi khi tôi thay đổi Mô hình không?
Vanuan

25

Nếu đó là một dự án mới, tôi sẽ bỏ cơ sở dữ liệu và tạo một dự án mới với bộ ký tự phù hợp:

CREATE DATABASE <dbname> CHARACTER SET utf8;

Xin vui lòng giúp kiểm tra câu hỏi này stackoverflow.com/questions/46348817/ từ
Vua

Trong trường hợp của tôi, db của chúng tôi được tạo bởi docker vì vậy để khắc phục tôi đã thêm đoạn sau vào lệnh db: lệnh: trong tệp soạn thảo của tôi:- --character-set-server=utf8
followben 11/11/18

1
Đơn giản vậy thôi. Cảm ơn @Vanuan
Enku

nếu đây không phải là một dự án mới, chúng tôi sẽ sao lưu từ db, thả nó và tạo lại nó với bộ ký tự utf8 và sau đó khôi phục lại bản sao lưu. Tôi đã làm điều đó trong dự án không mới ...
Mohammad Reza

8

Tôi chỉ tìm ra một phương pháp để tránh các lỗi trên.

Lưu vào cơ sở dữ liệu

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

Đây có phải là phương pháp duy nhất để lưu các chuỗi như thế vào bảng MySQL và giải mã nó trước khi kết xuất thành các mẫu để hiển thị?


12
Tôi đang gặp vấn đề tương tự, nhưng tôi không đồng ý rằng đây là một giải pháp hợp lệ. Khi bạn .encode('unicode_escape')không thực sự lưu trữ các ký tự unicode trong cơ sở dữ liệu. Bạn đang buộc tất cả các máy khách hủy mã hóa trước khi sử dụng chúng, điều đó có nghĩa là nó sẽ không hoạt động đúng với django.admin hoặc tất cả các loại khác.
muudscope

3
Mặc dù việc lưu trữ mã thoát thay vì ký tự có vẻ khó chịu, nhưng đây có lẽ là một trong vài cách để lưu các ký tự UTF-8 4 byte như biểu tượng cảm xúc trong utf8bộ ký tự 3 byte của MySQL 5.1 .
markpasc

2
Có một mã hóa được gọi là utf8mb4cho phép nhiều hơn Mặt phẳng đa ngôn ngữ cơ bản được lưu trữ. Tôi biết, bạn sẽ nghĩ "UTF8" là tất cả những gì cần thiết để lưu trữ Unicode đầy đủ. Vâng, whaddaya biết, nó không phải. Xem dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
Mihai Danila

@jack bạn có thể muốn xem xét thay đổi câu trả lời được chấp nhận thành câu trả lời hữu ích hơn
donturner

đó là một cách giải quyết khả thi, nhưng tôi không khuyên bạn nên sử dụng nó (như được ủng hộ bởi @muudscope). Tôi vẫn không thể lưu trữ, ví dụ, biểu tượng cảm xúc cho cơ sở dữ liệu mysql. Có ai đã hoàn thành nó?
Marcelo Sardelich 17/03/2016

6

Bạn có thể thay đổi đối chiếu trường văn bản của mình thành UTF8_general_ci và vấn đề sẽ được giải quyết.

Lưu ý, điều này không thể được thực hiện trong Django.


1

Bạn không cố lưu chuỗi unicode, bạn đang cố lưu bytestrings trong mã hóa UTF-8. Làm cho chúng chuỗi ký tự unicode thực tế:

user.last_name = u'Slatkevičius'

hoặc (khi bạn không có chuỗi ký tự) giải mã chúng bằng mã hóa utf-8:

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

@Thomas, tôi đã thử chính xác như những gì bạn nói nhưng nó vẫn phát sinh lỗi tương tự.
jack

0

Đơn giản chỉ cần thay đổi bảng của bạn, không cần bất kỳ điều gì. chỉ cần chạy truy vấn này trên cơ sở dữ liệu. THAY table_nameĐỔI BẢNG CHUYỂN ĐỔI ĐỂ TÙY CHỈNH utf8

Nó chắc chắn sẽ làm việc.


0

Cải thiện câu trả lời @madprops - giải pháp như một lệnh quản lý 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()

Hy vọng điều này sẽ giúp được ai ngoài tôi :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.