关于mysql:utf8-general-ci和utf8-unicode-ci有什么区别?

What's the difference between utf8_general_ci and utf8_unicode_ci

utf8_general_ciutf8_unicode_ci在性能上有什么区别吗?


这两个排序规则都适用于UTF-8字符编码。不同之处在于文本的排序和比较方式。

注:由于MySQL5.5.3应该使用utf8mb4,而不是utf8。它们都指的是UTF-8编码,但是旧的utf8有一个特定于mysql的限制,防止使用超过0xfffd的字符。

  • 准确度

    utf8mb4_unicode_ci是基于Unicode排序和比较标准的,该标准在非常广泛的语言范围内精确排序。

    utf8mb4_general_ci未能实现所有的unicode排序规则,这将导致在某些情况下(例如使用特定语言或字符时)出现不希望的排序。

  • 性能

    utf8mb4_general_ci在比较和排序方面速度更快,因为它采用了一系列与性能相关的快捷方式。

    在现代服务器上,这种性能提升几乎可以忽略不计。它是在服务器的CPU性能只占当今计算机的一小部分的时候设计的。

    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,除非准确的排序非常重要,足以证明性能成本的合理性。如今,性能成本几乎消失了,开发人员正在更加认真地对待国际化。

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


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

我创建了一个包含500000行的非常简单的表:

1
2
3
4
5
6
7
CREATE TABLE test(
  ID INT(11) DEFAULT NULL,
  Description VARCHAR(20) DEFAULT NULL
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci;

然后我通过运行以下存储过程将其填充为随机数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

然后,我创建了以下存储过程,对简单的SELECTSELECTLIKE进行基准测试,并对(SELECTORDER BY进行排序):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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_ciutf8_unicode_ci

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

我的研究结果是:

benchmark_simple_select()

  • utf8_general_ci时:9957 ms
  • utf8_unicode_ci时:10271 ms

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

benchmark_select_like()

  • utf8_general_ci时:11441 ms
  • utf8_unicode_ci时:12811 ms

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

benchmark_order_by()

  • 使用utf8_general_ci时:11944ms
  • utf8_unicode_ci时:12887 ms

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


这篇文章描述得很好。

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


简而言之:

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

但是,如果你对性能非常感兴趣——使用utf8_general_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-unicode-ci比utf-unicode-ci(应该实现整个标准)使用更小、更不正确的比较集。一般的_Ci集将更快,因为要做的计算更少。


一些细节(pl)

正如我们在这里看到的(彼得·古鲁特赞)波兰字母"?"的排序/比较有区别。(L和stroke-html-esc:Ł(小写:"?"-html-esc:ł——我们有以下假设:

1
2
3
4
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之前。这种编码中没有一种更好或更糟——这取决于您的需要。