MySQL非法混合排序规则

sf_*_*anb 18 mysql collation symfony fosuserbundle symfony-2.1

查看我的prod日志后,我提到了一些错误:

[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 '=' 
Run Code Online (Sandbox Code Playgroud)

Alghout我在学说cfg下有UTF-8默认值:

doctrine:
    dbal:
        charset:  UTF8
Run Code Online (Sandbox Code Playgroud)

好像我的所有MySQL表都在latin1_swedish_ci,所以我的问题是:

我是否可以手动更改utf8_general_ci所有表格的校对而无需任何复杂/预防措施?

egg*_*yal 60

理解以下定义很有帮助:

  • 字符编码详细每个符号如何以二进制表示(并因此存储在计算机).例如,符号é(U + 00E9,拉丁小字母E急性)被编码0xc3a9UTF-8 (其MySQL调用utf8)和0xe9窗口1252(其MySQL调用latin1).

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

  • 核对是在一个字符集的排序,以使字符串进行比较.例如:MySQL的latin1_swedish_ci排序规则将字符的大多数重音变体视为等同于基本字符,而其latin1_general_ci排序规则将在下一个基本字符之前对它们进行排序,但不是等效的(还有其他更重要的差异:例如字符的顺序喜欢å,ä,öß).

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

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

  1. 一个值fos_user.username,在列的字符集(Windows-1252)中编码,表示对其整理的优先级latin1_swedish_ci(强制值为2); 同

  2. 字符串文字'Nrv?Kasi',在连接的字符集(UTF-8,由Doctrine配置)中编码,并表示对连接的排序规则的首选项utf8_general_ci(强制值为4).

由于这些字符串中的第一个具有比第二个字符串更低的强制性值,因此MySQL尝试使用该字符串的校对来执行比较:latin1_swedish_ci.为此,MySQL尝试将第二个字符串转换为latin1-but,因为该?字符集中不存在该字符,因此比较失败.


警告

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

如果你认为该列确实包含这样的字符,那么你可能写入列时连接字符编码被设置为某些东西(例如latin1)导致MySQL将接收到的字节序列解释为全部在Windows-1252字符中的字符组.

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

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

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET foo;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 删除与这些列关联的编码信息,方法是将它们转换为binary字符集:

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET binary;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 通过将数据转换为相关字符集,将数据实际传输的编码与这些列相关联.

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET bar;
    
    Run Code Online (Sandbox Code Playgroud)

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


一旦确定列被正确编码,就可以强制通过以下任一方式使用Unicode整理进行比较 -

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

我是否可以手动更改utf8_general_ci所有表格的校对而无需任何复杂/预防措施?

主要考虑因素是Unicode编码比单字节字符集占用更多空间,因此:

  • 可能需要更多存储空间;

  • 比较可能会更慢; 和

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

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

要更改默认的表字符集和所有字符列(CHAR,VARCHAR,TEXT)到一个新的字符集,使用这样的语句:

ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;

对于具有数据类型VARCHARTEXT类型之一的列,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用于更改单个列.

  • 忽略特里斯坦.这非常有用.谢谢你把时间花在这个蛋上.做得太好了. (4认同)
  • 哇,不要在解决方案上花 2 天时间 ;-) 但这是迄今为止见过的最完整的解决方案,所以谢谢。 (2认同)
  • 请注意,当在另一个字符集中编码任一字符串没有问题时,也会出现"非法混合排序",但是应该使用哪种排序规则存在歧义.我在[更新的答案](http://stackoverflow.com/a/21061305)中讨论了这个案例. (2认同)

JGu*_*ezC 8

那就对了.我遇到了这个问题,最好的快速和快速的解决方案是

         CONVERT(fos_user.username USING utf8)
Run Code Online (Sandbox Code Playgroud)