MySQL 非法混合排序规则

Posted

技术标签:

【中文标题】MySQL 非法混合排序规则【英文标题】:MySQL Illegal mix of collations 【发布时间】:2012-08-28 03:32:00 【问题描述】:

查看我的产品日志后,我有一些错误提示:

[2012-08-31 15:56:43] request.CRITICAL: Doctrine\DBAL\DBALException: 
An exception occurred while executing 'SELECT t0.username ....... FROM fos_user t0 WHERE t0.username = ?'
with params "1":"Nrv\u29e7Kasi":

SQLSTATE[HY000]: General error: 1267 Illegal mix of collations (latin1_swedish_ci,IMPLICIT)
and (utf8_general_ci,COERCIBLE) for operation '=' 

Alghout 我在学说 cfg 下默认使用 UTF-8:

doctrine:
    dbal:
        charset:  UTF8

看来我所有的 mysql 表都在latin1_swedish_ci,所以我的问题是:

我可以手动将所有表格的排序规则更改为utf8_general_ci,而不会出现任何并发症/预防措施吗?

【问题讨论】:

Relevant 【参考方案1】:

通过如下命令简单地转换表格的字符集,

ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8;

【讨论】:

【参考方案2】:

没错。我遇到了这个问题,最好的快速解决方案是

         CONVERT(fos_user.username USING utf8)

【讨论】:

【参考方案3】:

了解以下定义会有所帮助:

字符编码详细说明了每个符号如何以二进制表示(因此存储在计算机中)。例如,符号é(U+00E9,拉丁文小写字母E,带尖音符)是encoded,在UTF-8(MySQL 调用utf8)中为0xc3a9,在Windows-1252(其中MySQL 调用 latin1)。

字符集是可以使用给定字符编码表示的符号字母表。令人困惑的是,该术语也用于表示与字符编码相同的含义。

collat​​ion 是对字符集的排序,以便可以比较字符串。例如:MySQL 的 latin1_swedish_ci 排序规则将字符的大多数重音变体视为等同于基本字符,而其 latin1_general_ci 排序规则会将它们排序在下一个基本字符之前但不等同(还有其他更重要的差异:比如åäöß等字符的顺序。

MySQL 将决定应将哪种排序规则应用于给定表达式,如 Collation of Expressions 中所述:特别是,列的排序规则优先于字符串文字的排序规则。

查询的WHERE 子句比较以下字符串:

    fos_user.username 中的一个值,以列的字符集 (Windows-1252) 编码,并表示对其排序规则 latin1_swedish_ci 的偏好(强制值为 2);与

    字符串文字'Nrv⧧Kasi',以连接的字符集(UTF-8,由 Doctrine 配置)编码并表示对连接排序规则utf8_general_ci 的偏好(强制值为 4)。

由于这些字符串中的第一个具有比第二个更低的强制值,MySQL 尝试使用该字符串的排序规则执行比较:latin1_swedish_ci。为此,MySQL 尝试将第二个字符串转换为 latin1,但由于该字符集中不存在 字符,因此比较失败。


警告

应该暂停一下,考虑一下当前列的编码方式:您正在尝试过滤fos_user.username 等于包含不能字符的字符串的记录存在于该列中

如果您认为列确实包含此类字符,那么您可能在连接字符编码设置为导致 MySQL 解释接收到字节序列为所有在 Windows-1252 字符集中的字符。

如果是这种情况,在继续之前,您应该修复您的数据!

    将这些列转换为数据插入时使用的字符编码(如果与现有编码不同):

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET foo;
    

    通过将这些列转换为binary 字符集来删除与这些列关联的编码信息:

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET binary;
    

    通过将这些列转换为相关字符集,将实际传输数据的编码与这些列相关联。

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET bar;
    

请注意,如果从多字节编码转换,您可能需要增加列的大小(甚至更改其类型)以适应转换后字符串的最大可能长度。


一旦确定列被正确编码,就可以强制使用 Unicode 排序规则进行比较——

将值 fos_user.username 显式转换为 Unicode 字符集:

WHERE CONVERT(fos_user.username USING utf8) = ?

强制字符串文字具有比列更低的强制值(将导致列的值隐式转换为 UTF-8):

WHERE fos_user.username = ? COLLATE utf8_general_ci

或者,正如您所说,可以将列永久转换为 Unicode 编码并适当地设置其排序规则。

我可以将所有表格的排序规则手动更改为utf8_general_ci,而不会出现任何并发症/预防措施吗?

原则上考虑是Unicode编码比单字节字符集占用更多空间,所以:

可能需要更多存储空间;

比较可能会更慢;和

可能需要调整索引前缀长度(注意最大值以字节为单位,因此表示的字符可能比以前少)。

另外,请注意,如 ALTER TABLE Syntax 中所述:

要将表默认字符集和所有字符列(CHARVARCHARTEXT)更改为新字符集,请使用如下语句:

ALTER TABLE tbl_name 转换为字符集 charset_name;

对于数据类型为VARCHARTEXT 类型之一的列,CONVERT TO CHARACTER SET 将根据需要更改数据类型,以确保新列足够长以存储与原始列。例如,TEXT 列有两个长度字节,用于存储列中值的字节长度,最大值为 65,535。对于latin1TEXT 列,每个字符需要一个字节,因此该列最多可以存储65,535 个字符。如果将列转换为utf8,则每个字符最多可能需要三个字节,最大可能长度为 3 × 65,535 = 196,605 字节。该长度不适合 TEXT 列的长度字节,因此 MySQL 会将数据类型转换为 MEDIUMTEXT,这是长度字节可以记录值 196,605 的最小字符串类型。同样,VARCHAR 列可能会转换为 MEDIUMTEXT

为避免刚刚描述的类型的数据类型更改,请勿使用CONVERT TO CHARACTER SET。相反,请使用 MODIFY 更改各个列。

【讨论】:

哇,不要在解决方案上花费 2 天 ;-) 但这是迄今为止见过的最完整的解决方案,非常感谢。 忽略特里斯坦。这是超级有用的。谢谢你把时间花在这个鸡蛋上。做得很好。 请注意,当在另一个字符集中对任何一个字符串进行编码没有问题时,也会出现“非法混合排序规则”,但应该使用哪种排序规则存在歧义。我已经在a newer answer讨论过这个案例。

以上是关于MySQL 非法混合排序规则的主要内容,如果未能解决你的问题,请参考以下文章

MySQL某些字符导致“非法混合排序规则”错误

MySQL 视图 - 排序规则的非法混合

MySql 中的非法混合排序规则错误

排序规则的非法混合 MySQL 错误

出现问题:MySQL中的排序规则的非法混合[重复]

如何解决“非法混合排序规则”SQLException?