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
)。
字符集是可以使用给定字符编码表示的符号字母表。令人困惑的是,该术语也用于表示与字符编码相同的含义。
collation 是对字符集的排序,以便可以比较字符串。例如: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 中所述:
要将表默认字符集和所有字符列(
CHAR
、VARCHAR
、TEXT
)更改为新字符集,请使用如下语句:ALTER TABLE tbl_name 转换为字符集 charset_name;对于数据类型为
VARCHAR
或TEXT
类型之一的列,CONVERT TO CHARACTER SET
将根据需要更改数据类型,以确保新列足够长以存储与原始列。例如,TEXT
列有两个长度字节,用于存储列中值的字节长度,最大值为 65,535。对于latin1
TEXT
列,每个字符需要一个字节,因此该列最多可以存储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 非法混合排序规则的主要内容,如果未能解决你的问题,请参考以下文章