在utf8_general_ci和utf8_unicode_ci之间,在性能方面有什么不同吗?


请参阅mysql手册,Unicode字符集部分:

For any Unicode character set, operations performed using the _general_ci collation are faster than those for the _unicode_ci collation. For example, comparisons for the utf8_general_ci collation are faster, but slightly less correct, than comparisons for utf8_unicode_ci. The reason for this is that utf8_unicode_ci supports mappings such as expansions; that is, when one character compares as equal to combinations of other characters. For example, in German and some other languages “ß” is equal to “ss”. utf8_unicode_ci also supports contractions and ignorable characters. utf8_general_ci is a legacy collation that does not support expansions, contractions, or ignorable characters. It can make only one-to-one comparisons between characters.

因此,总的来说,utf_general_ci使用的比较集比utf_unicode_ci更小,更不正确(根据标准),后者应该实现整个标准。general_ci集将更快,因为要做的计算更少。


对于那些在2020年或以后仍然面临这个问题的人来说,有一些更新的选择可能比这两个都要好。例如,utf8_unicode_520_ci。

所有这些排序规则都适用于UTF-8字符编码。区别在于如何对文本进行排序和比较。

_unicode_ci and _general_ci are two different sets of rules for sorting and comparing text according to the way we expect. Newer versions of MySQL introduce new sets of rules, too, such as _unicode_520_ci for equivalent rules based on Unicode 5.2, or the MySQL 8.x specific _0900_ai_ci for equivalent rules based on Unicode 9.0 (and with no equivalent _general_ci variant). People reading this now should probably use one of these newer collations instead of either _unicode_ci or _general_ci. The description of those older collations below is provided for interest only.

MySQL目前正在从旧的、有缺陷的UTF-8实现过渡。现在,对于字符编码部分,您需要使用utf8mb4而不是utf8,以确保您得到的是固定版本。为了向后兼容,这个有缺陷的版本仍然存在,尽管它已被弃用。

关键的不同点

utf8mb4_unicode_ci is based on the official Unicode rules for universal sorting and comparison, which sorts accurately in a wide range of languages. utf8mb4_general_ci is a simplified set of sorting rules which aims to do as well as it can while taking many short-cuts designed to improve speed. It does not follow the Unicode rules and will result in undesirable sorting or comparison in some situations, such as when using particular languages or characters. On modern servers, this performance boost will be all but negligible. It was devised in a time when servers had a tiny fraction of the CPU performance of today's computers.

utf8mb4_unicode_ci相对于utf8mb4_general_ci的优点

utf8mb4_unicode_ci使用Unicode规则进行排序和比较,它使用相当复杂的算法在各种语言和使用各种特殊字符时进行正确排序。这些规则需要考虑到特定于语言的惯例;不是每个人都按照我们所说的“字母顺序”来排列他们的字符。

就拉丁(即“欧洲”)语言而言,在MySQL中Unicode排序和简化的utf8mb4_general_ci排序之间没有太大区别,但仍然有一些区别:

例如,Unicode排序规则将“ß”分类为“ss”,将“”分类为“OE”,就像使用这些字符的人通常希望的那样,而utf8mb4_general_ci将它们分类为单个字符(分别类似于“s”和“e”)。 一些Unicode字符被定义为可忽略的,这意味着它们不应该计入排序顺序,比较应该转移到下一个字符。Utf8mb4_unicode_ci可以正确地处理这些。

在非拉丁语言中,例如亚洲语言或具有不同字母的语言,Unicode排序和简化的utf8mb4_general_ci排序之间可能存在更多差异。utf8mb4_general_ci的适用性很大程度上取决于所使用的语言。对于某些语言,这是非常不够的。

你应该用什么?

几乎可以肯定没有理由再使用utf8mb4_general_ci了,因为我们已经忽略了CPU速度低到足以使性能差异变得重要的情况。您的数据库几乎肯定会受到其他瓶颈的限制。

在过去,一些人建议使用utf8mb4_general_ci,除非精确排序非常重要,足以证明性能成本是合理的。今天,这种性能成本几乎消失了,开发人员正在更加认真地对待国际化。

有一种说法是,如果速度对你来说比准确性更重要,你可能根本不做任何排序。如果你不需要一个算法准确,那么让它更快是微不足道的。因此,utf8mb4_general_ci是一种折衷方案,可能出于速度原因不需要,也可能出于准确性原因不适合。

我要补充的另一件事是,即使您知道应用程序只支持英语,它可能仍然需要处理人名,这些人名通常包含在其他语言中使用的字符,在这些语言中正确排序同样重要。对所有内容使用Unicode规则有助于让人安心,因为非常聪明的Unicode人员非常努力地使排序正常工作。

这些部分意味着什么

Firstly, ci is for case-insensitive sorting and comparison. This means it's suitable for textual data, and case is not important. The other types of collation are cs (case-sensitive) for textual data where case is important, and bin, for where the encoding needs to match, bit for bit, which is suitable for fields which are really encoded binary data (including, for example, Base64). Case-sensitive sorting leads to some weird results and case-sensitive comparison can result in duplicate values differing only in letter case, so case-sensitive collations are falling out of favor for textual data - if case is significant to you, then otherwise ignorable punctuation and so on is probably also significant, and a binary collation might be more appropriate.

Next, unicode or general refers to the specific sorting and comparison rules - in particular, the way text is normalized or compared. There are many different sets of rules for the utf8mb4 character encoding, with unicode and general being two that attempt to work well in all possible languages rather than one specific one. The differences between these two sets of rules are the subject of this answer. Note that unicode uses rules from Unicode 4.0. Recent versions of MySQL and MariaDB add the rulesets unicode_520 using rules from Unicode 5.2, and MySQL 8.x adds 0900 (dropping the "unicode_" part) using rules from Unicode 9.0.

最后,utf8mb4当然是内部使用的字符编码。在这个回答中,我只讨论基于Unicode的编码。


这篇文章描述得很好。

简而言之:utf8_unicode_ci使用Unicode标准中定义的Unicode排序算法,而utf8_general_ci是一种更简单的排序顺序,会导致“不太准确”的排序结果。


我想知道使用utf8_general_ci和utf8_unicode_ci之间的性能差异是什么,但我没有在互联网上找到任何基准测试,所以我决定自己创建基准测试。

我创建了一个非常简单的50万行表:

CREATE TABLE test(
  ID INT(11) DEFAULT NULL,
  Description VARCHAR(20) DEFAULT NULL
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci;

然后我通过运行这个存储过程,用随机数据填充它:

CREATE PROCEDURE randomizer()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE random CHAR(20) ;
  theloop: loop
    SET random = CONV(FLOOR(RAND() * 99999999999999), 20, 36);
    INSERT INTO test VALUES (i+1, random);
    SET i=i+1;
    IF i = 500000 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END

然后我创建了以下存储过程来测试简单的SELECT, SELECT with LIKE和排序(SELECT with ORDER BY):

CREATE PROCEDURE benchmark_simple_select()
BEGIN
  DECLARE i INT DEFAULT 0;
  theloop: loop
    SELECT *
    FROM test
    WHERE Description = 'test' COLLATE utf8_general_ci;
    SET i = i + 1;
    IF i = 30 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END;

CREATE PROCEDURE benchmark_select_like()
BEGIN
  DECLARE i INT DEFAULT 0;
  theloop: loop
    SELECT *
    FROM test
    WHERE Description LIKE '%test' COLLATE utf8_general_ci;
    SET i = i + 1;
    IF i = 30 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END;

CREATE PROCEDURE benchmark_order_by()
BEGIN
  DECLARE i INT DEFAULT 0;
  theloop: loop
    SELECT *
    FROM test
    WHERE ID > FLOOR(1 + RAND() * (400000 - 1))
    ORDER BY Description COLLATE utf8_general_ci LIMIT 1000;
    SET i = i + 1;
    IF i = 10 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END;

在上面的存储过程中使用了utf8_general_ci排序,但当然,在测试期间,我同时使用了utf8_general_ci和utf8_unicode_ci。

对于每种排序,我调用每个存储过程5次(utf8_general_ci调用5次,utf8_unicode_ci调用5次),然后计算平均值。

我的结果是:

benchmark_simple_select ()

utf8_general_ci: 9,957 ms utf8_unicode_ci: 10,271 ms

在这个基准测试中,使用utf8_unicode_ci比使用utf8_general_ci慢3.2%。

benchmark_select_like ()

使用utf8_general_ci: 11,441 ms 使用utf8_unicode_ci: 12,811 ms

在这个基准测试中,使用utf8_unicode_ci比使用utf8_general_ci慢12%。

benchmark_order_by ()

使用utf8_general_ci: 11,944 ms 使用utf8_unicode_ci: 12,887 ms

在这个基准测试中,使用utf8_unicode_ci比使用utf8_general_ci慢7.9%。


简单来说:

如果您需要更好的排序顺序-使用utf8_unicode_ci(这是首选方法),

但是如果您对性能非常感兴趣,可以使用utf8_general_ci,但要知道它有点过时了。

性能方面的差异非常微小。


一些细节(PL)

正如我们可以在这里(Peter Gulutzan)读到的,排序/比较波兰字母“Ł”(L与笔画- html esc: Ł)(小写:“ova”- html esc: ł) -我们有以下假设:

utf8_polish_ci      Ł greater than L and less than M
utf8_unicode_ci     Ł greater than L and less than M
utf8_unicode_520_ci Ł equal to L
utf8_general_ci     Ł greater than Z

在波兰语中,字母Ł在字母L之后,在字母m之前,没有哪个编码更好或更差——这取决于你的需要。


根据这篇文章,在MySQL 5.7上使用utf8mb4_general_ci而不是utf8mb4_unicode_ci有相当大的性能优势: https://www.percona.com/blog/2019/02/27/charset-and-collation-settings-impact-on-mysql-performance/


排序和字符匹配有两个很大的区别:

排序:

Utf8mb4_general_ci删除所有重音并逐个排序,这可能会产生不正确的排序结果。 Utf8mb4_unicode_ci排序准确。

字符匹配

它们以不同的方式匹配字符。

例如,在utf8mb4_unicode_ci中,你有i !=伊斯坦布尔,但在utf8mb4_general_ci中,它包含了伊斯坦布尔=伊斯坦布尔。

例如,假设您有一个name=" yilmaz "的行。然后

select id from users where name='Yilmaz';

如果搭配为utf8mb4_general_ci,则返回该行,但如果搭配为utf8mb4_unicode_ci,则不会返回该行!

另一方面,我们在utf8mb4_unicode_ci中有a=ª和ß=ss,而在utf8mb4_general_ci中则不是这样。所以想象你有一行的名字="ªßi",然后

select id from users where name='assi';

如果并置为utf8mb4_unicode_ci则返回行,但如果并置设置为utf8mb4_general_ci则不返回行。

每个搭配的完整列表可以在这里找到。


上面的注释表明没有理由使用utf8_general*。然而,对于日本人来说,这可能不是真的。

在MariaDB中,utf8mb4_ja_0900_as_cs不可用,因此必须使用unicode或通用选项之一。然而,unicode对待浊音和浊音等是一样的。例如,びよういん(美容师)被视为等同于びょういん(医院)。这显然是不正确的行为。

> select strcmp('が', 'か' collate utf8mb4_unicode_ci); #0
> strcmp('びよういん', 'びょういん' collate utf8mb4_unicode_520_ci); #0 

而将军给出

> select strcmp('が', 'か' collate utf8mb4_general_ci); #1

换句话说,unicode对浊音假名和浊音假名一视同仁。在我看来,这并不可取。

编辑:使用uca1400_ai_cs可能更好,它在MariaDB的新版本中可用,并使上述排序正确。