澄清重音字符与非二进制排序规则相等的潜在问题

dna*_*nag 6 mysql collation non-ascii-characters

对于有国际支持的网站,我在大多数表格和列中使用utf8mb4字符集和utf8mb4_unicode_ci排序规则.性能不是最重要的,各种语言的准确排序很重要.

我理解utf8mb4_general_ci和utf8mb4_unicode_ci排序规则如何与重音字符进行比较,即:

SELECT column FROM table WHERE column='abad';
Run Code Online (Sandbox Code Playgroud)

将返回'abad'和'abád'

在研究MySQL中的utf8支持时,我遇到了非二进制utf8___校对的假设问题.http://mzsanford.com/blog/mysql-and-unicode/上的页面描述了在某些更新中未保存更改的问题.他说'在更新记录时,似乎MySQL(或至少InnoDB)在更新记录之前检查是否相等.由于整数版本认为只有重音更改是相同的,因此MySQL会跳过写入(这会节省I/O开销)并返回成功,因为它认为它优化了写入而不是失败."

我将其解释为:如果您尝试更新仅在字段的重音符号中进行更改的记录,则它将无法正确更新(因为MySQL认为它已经匹配).但我无法复制这个.我创建了一个简单的测试用例:

CREATE DATABASE test_utf8 
    CHARACTER SET utf8mb4 
    COLLATE utf8mb4_unicode_ci;

USE test_utf8;

CREATE TABLE test (
    id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, 
    text VARCHAR(300) NOT NULL, 
    PRIMARY KEY (id)
) ENGINE = INNODB;

INSERT INTO test (text) VALUES ('abád');

UPDATE test SET text='abad' WHERE id=1;
Run Code Online (Sandbox Code Playgroud)

但是,这会正确更新值(尽管只有一个字符的重音更改).这可能只是旧版MySQL中的一个问题吗?或者这个问题是否会在略有不同的情况下出现?


如果你有时间阅读我关于这个主题的一些概念的一些说明,看看我是否有任何误解,我也将不胜感激.如果它没有错误,也许它对某人来说是有用的信息.

MySQL的utf8字符集不提供真正的utf8支持,因为字符只有1-3个字节.要获得真正的utf8支持,您可能需要使用utf8mb4.

一般来说,utf8mb4_unicode_ci在语言适当的排序方面会更准确,但与使用utf8mb4_general_ci相比,会有轻微的性能损失.

如果某些列不需要排序,并且将使用比较/相等检查,则应使用utf8mb4_bin,因为它会稍快一些.

在utf8mb4_general_ci和utf8mb4_unicode_ci排序规则中,重音字符被视为相等.因此,对于必须具有唯一值(例如主键)的列,这是一个错误的排序规则选择.在这种情况下,应该使用utf8mb4_bin.如果一个字段需要具有唯一性的重音感知,但也需要在某个时候进行语言排序,它可以存储为utf8mb4_bin,并且您可以在排序时在查询中使用collat​​e子句.例如:

SELECT column FROM table ORDER BY column COLLATE utf8mb4_unicode_ci;
Run Code Online (Sandbox Code Playgroud)

这将导致排序语言排序,尽管其内部存储在二进制排序规则中.这将影响性能,因为字段的整理决定了它的索引方式.查询的性能差异类似于对未索引列与索引列进行排序时的性能差异.

默认情况下,utf8mb4_unicode_ci或utf8mb4_general_ci排序规则下的搜索不会重音,因此搜索'abad'会返回'abad'和'abád'.因此,如果您需要重音感知搜索,则必须将列的排序规则设置为utf8mb4_binary(如果所有搜索都是重音敏感的)或在查询中使用collat​​e子句(如果您希望大多数搜索都是重音盲).由于utf8mb4_bin排序规则区分大小写,因此如果您需要不区分大小写但需要重音的搜索,则还需要修改查询.例如(假设您的搜索词已经在服务器端脚本语言中变成小写):

(Assuming the data is stored with a collation of utf8mb4_bin)
SELECT column FROM table WHERE LOWERCASE(column) LIKE 'abád';

(Assuming the data is stored with a collation of utf8mb4_unicode_ci)
SELECT column FROM table WHERE LOWERCASE(column) LIKE 'abád' COLLATE utf8mb4_bin;
Run Code Online (Sandbox Code Playgroud)

此外,从MySQL文档(仅包括其他人):比较来自不同列的值时,尽可能声明具有相同字符集和排序规则的列,以避免在运行查询时进行字符串转换.

Chr*_*wan 1

我不是专家,但我尝试了你所做的一些额外的事情......

\n\n

我在 MySQL 5.6.17 上运行了您的设置和以下内容:

\n\n
SELECT COUNT(*) FROM test WHERE `text`='abad';\nSELECT COUNT(*) FROM test WHERE `text`='ab\xc3\xa1d';\nUPDATE test SET text='ab\xc3\xa1d' WHERE id=1;\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如我们所期望的,选择都返回 1 行,并且更新(如您的更新)修改 1 行,这与博客的建议相反。

\n\n

我认为这可能是一个较低级别的优化,但当我尝试在命令行客户端(而不是工作台)中再次运行它时,我注意到一些有趣的事情:

\n\n
mysql> SELECT COUNT(*) FROM test WHERE `text`='ab\xc3\xa1d';\nERROR 1267 (HY000): Illegal mix of collations (utf8mb4_unicode_ci,IMPLICIT) and\n(utf8_general_ci,COERCIBLE) for operation '='\nmysql> UPDATE test SET text='ab\xc3\xa1d' WHERE id=1;\nERROR 1366 (HY000): Incorrect string value: '\\xA0d' for column 'text' at row 1\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以我运行这个来看看发生了什么:

\n\n
mysql> SELECT collation('ab\xc3\xa1d');\n+-------------------+\n| collation('ab\xc3\xa1d')  |\n+-------------------+\n| utf8_general_ci   |\n+-------------------+\n1 row in set (0.00 sec)\n
Run Code Online (Sandbox Code Playgroud)\n\n

由于我的会话集,一定会发生一些强制......所以我尝试显式匹配:

\n\n
UPDATE test SET text='abad' COLLATE utf8_unicode_ci WHERE id=1;\nUPDATE test SET text='ab\xc3\xa1d' COLLATE utf8_unicode_ci WHERE id=1;\n
Run Code Online (Sandbox Code Playgroud)\n\n

我仍然得到了相同的结果(两次更新)。

\n\n

现在,我的猜测是,InnoDB 的优化是在比根据文本条件进行 SELECT 更低的级别上完成的。

\n