MySQL 不合法的字符集混合
MySQL 不合法的字符集混合
在查看了我的生产日志后,我发现出现了一些错误提及:
[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 '='
虽然在Doctrine配置中我使用了UTF-8默认值:
doctrine: dbal: charset: UTF8
但是似乎我所有的MySQL表都使用的是latin1_swedish_ci
编码,我的问题是:
我可以手动更改所有表的编码为utf8_general_ci
吗,而不会引起任何问题或需要注意的事项吗?
理解以下定义是很有帮助的:
-
字符编码详细说明每个符号在二进制中的表示方式(因此存储在计算机中)。例如,符号
é
(U+00E9,带有重音的小写拉丁字母 E)在UTF-8编码中(MySQL称之为utf8
)被编码为0xc3a9
,在Windows-1252编码中(MySQL称之为latin1
)被编码为0xe9
。 -
字符集是使用给定字符编码可以表示的符号的字母表。令人困惑的是,该术语也被用来表示与字符编码相同的含义。
-
排序规则是在一个字符集上对字符串进行排序的方法,以便可以比较字符串。例如:MySQL的
latin1_swedish_ci
排序规则将大多数音调变化的字符视为与基本字符等效,而其latin1_general_ci
排序规则将其排序在下一个基本字符之前但不等效(还有其他更重大的差异:例如å
、ä
、ö
和ß
等字符的顺序)。
如表达式的排序规则所述,MySQL将决定应用于特定表达式的排序规则:特别是,一个列的排序规则优先于一个字符串文字的排序规则。
查询语句的WHERE
子句比较以下字符串:
-
fos_user.username
列中以该列字符集(Windows-1252)编码且表达其排序规则为latin1_swedish_ci
(具有强制性值2)的值;以及 -
在连接的字符集(由Doctrine配置的UTF-8)中编码且表达其偏好排序规则为连接的排序规则
utf8_general_ci
(具有强制性值4)的字符串文字'Nrv⧧Kasi'
。
由于第一个字符串的强制性值低于第二个字符串,MySQL尝试使用该字符串的排序规则:latin1_swedish_ci
。为此,MySQL尝试将第二个字符串转换为latin1
,但由于⧧
字符不存在于该字符集中,因此比较失败。
警告
应该暂停一下,考虑列当前的编码方式:您尝试过滤包含在该列中无法存在的字符的记录!
如果您认为该列确实包含这样的字符,那么您可能是在设置连接字符编码为某些内容(例如latin1
)的情况下写入了该列,导致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 CONVERT TO CHARACTER SET 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
来改变单个列。