ChatGPT解决这个技术问题 Extra ChatGPT

utf8_general_ci 和 utf8_unicode_ci 有什么区别?

utf8_general_ciutf8_unicode_ci 之间,在性能方面有什么不同吗?

如果您喜欢 utf8[mb4]_unicode_ci,您可能会更喜欢 utf8[mb4]_unicode_520_ci
我不知道我对此有何感受 - 他们没有修复他们的实现以遵循最新的 Unicode 标准,而是将过时的版本作为默认版本,人们现在必须添加“520”才能使用正确的版本。而且它不向前和向后兼容,因为你不能在旧的 MySQL 版本上使用“520”版本。为什么他们不能更新他们现有的排序规则?和“mb4”一样,真的。哪些代码真正依赖于旧的、有限/过时的行为来证明将其保留为默认行为是合理的?
更好的是 8.0 的默认值 utf8mb4_0900_ai_ci
8.0 显着加快了 utf8 比较。 (可能是 utf8/utf8mb4 的所有排序规则)

t
thomasrutter

对于那些在 2020 年或之后仍会遇到此问题的人,有一些更新的选项可能比这两个更好。例如,utf8_unicode_520_ci

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

_unicode_ci_general_ci 是两组不同的规则,用于按照我们期望的方式对文本进行排序和比较。较新版本的 MySQL 也引入了新的规则集,例如 _unicode_520_ci 用于基于 Unicode 5.2 的等效规则,或 MySQL 8.x 特定 _0900_ai_ci 用于基于 Unicode 9.0 的等效规则(并且没有等效的 _general_ci变体)。现在阅读本文的人可能应该使用这些较新的排序规则之一,而不是 _unicode_ci_general_ci。下面对那些较旧的排序规则的描述仅供参考。

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

主要区别

utf8mb4_unicode_ci 基于官方 Unicode 规则进行通用排序和比较,可在多种语言中准确排序。

utf8mb4_general_ci 是一组简化的排序规则,旨在尽其所能,同时采用许多旨在提高速度的捷径。它不遵循 Unicode 规则,并且在某些情况下会导致不希望的排序或比较,例如在使用特定语言或字符时。在现代服务器上,这种性能提升几乎可以忽略不计。它是在服务器的 CPU 性能只有当今计算机的一小部分时设计的。

utf8mb4_unicode_ci 优于 utf8mb4_general_ci

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

就拉丁语(即“欧洲”)语言而言,Unicode 排序和 MySQL 中简化的 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 人员已经非常努力地工作以使排序正常工作。

零件是什么意思

首先,ci 用于不区分大小写的排序和比较。这意味着它适用于文本数据,大小写并不重要。其他类型的排序规则是 cs(区分大小写),用于区分大小写的文本数据,以及 bin,用于编码需要逐位匹配的地方,适用于真正编码二进制数据的字段(包括,例如,Base64)。区分大小写的排序会导致一些奇怪的结果,区分大小写的比较可能会导致重复值仅在字母大小写上有所不同,因此区分大小写的排序规则对文本数据不受欢迎 - 如果大小写对您很重要,那么标点符号就可以忽略等等可能也很重要,二进制排序规则可能更合适。

接下来,unicodegeneral 指的是特定的排序和比较规则 - 特别是文本标准化或比较的方式。 utf8mb4 字符编码有许多不同的规则集,其中 unicodegeneral 是两个试图在所有可能的语言而不是一种特定的语言中都能正常工作的规则。这两组规则之间的差异是此答案的主题。请注意,unicode 使用 Unicode 4.0 中的规则。 MySQL 和 MariaDB 的最新版本使用 Unicode 5.2 中的规则添加了规则集 unicode_520,而 MySQL 8.x 使用 Unicode 9.0 中的规则添加了 0900(删除了“unicode_”部分)。

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


@KahWeeTeng 您应该永远,永远使用 utf8_general_ci:它根本行不通。这是 50 年前 ASCII 古怪的糟糕日子的倒退。如果没有来自 UCD 的 foldcase 映射,则无法完成 Unicode 不区分大小写的匹配。例如,“Σίσυφος”中有三个不同的 sigma;或者“TSCHüẞ”的小写字母是“tschüβ”,而“tschüβ”的大写字母是“TSCHÜSS”。你可以是对的,或者你可以很快。因此你必须使用utf8_unicode_ci,因为如果你不关心正确性,那么让它无限快是微不足道的。
Base64 编码不只是编码为 ASCII 吗?为什么排序规则的“bin”部分与 Base64 相关?
@BrianTristamWilliams 排序规则是指文本比较和排序的工作方式。 “bin”作为排序规则意味着它只是一个二进制比较:不会尝试适应任何书面语言约定,它将纯粹根据数据位进行比较。
@nightcoder 提到的性能提升并没有让我觉得可以忽略不计。我不会忽略 3% 的收益,而 12% 的收益更大,尤其是当任何数据库管理员都会做出数十个甚至数百个影响性能的选择时,它们会加起来。更重要的是,有时正确性并不重要。我的大多数数据库都需要容纳非基本拉丁编码中的 unicode 字符,但很少需要按这些字符对它们进行准确排序,事实上,我想不出一个实例我需要这个整个20多年的职业生涯。
然而,我怀疑真实世界数据的性能提升是否与@nightcoder 声称的一样大。该示例填充了随机数据。我数据库中的绝大多数数据主要是拉丁编码中存在的字符,只有偶尔会加入其他字符,而这些字符在排序中几乎不重要。可能是我同意你的结论,但出于不同的原因。如果大多数真实数据的性能提升可以忽略不计,我很乐意根据一些假设的未来需求选择正确性。
A
Alessio Cantarella

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

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

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

然后我创建了以下存储过程来对简单的 SELECTSELECTLIKE 和排序(SELECTORDER 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_ciutf8_unicode_ci

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

我的结果是:

benchmark_simple_select()

使用 utf8_general_ci:9,957 毫秒

使用 utf8_unicode_ci:10,271 毫秒

在这个基准测试中,使用 utf8_unicode_ciutf8_general_ci 慢 3.2%。

benchmark_select_like()

使用 utf8_general_ci:11,441 毫秒

使用 utf8_unicode_ci:12,811 毫秒

在这个基准测试中,使用 utf8_unicode_ciutf8_general_ci 慢 12%。

benchmark_order_by()

使用 utf8_general_ci:11,944 毫秒

使用 utf8_unicode_ci:12,887 毫秒

在此基准测试中,使用 utf8_unicode_ciutf8_general_ci 慢 7.9%。


很好的基准,谢谢分享。我得到了明显相似的数字(Windows 上的 MySQL v5.6.12):10%、4%、8%。我同意:utf8_general_ci 的性能提升太小了,不值得使用。
1)但是这个基准不应该根据定义为两个排序规则生成相似的结果吗?我的意思是 CONV(FLOOR(RAND() * 99999999999999), 20, 36) 只生成 ASCII,并且没有 Unicode 字符由排序规则算法处理。 2) Description = 'test' COLLATE ...Description LIKE 'test%' COLLATE ... 在运行时只处理一个字符串(“test”),不是吗? 3)在实际应用程序中,排序中使用的列可能会被索引,并且不同排序规则与真正的非 ASCII 文本的索引速度可能会有所不同。
@HalilÖzgür - 你的观点部分错误。我想这不是关于代码点值在 ASCII 之外(general_ci 可以正确处理),而是关于特定功能,比如将变音符号处理为“Umleaute”或一些这样的微妙之处。
因此,虽然这些性能提升看起来令人信服,但我想知道这是否适用于现实世界的数据。您正在使用随机字符填充这些字段,但在现实世界中,数据具有更多结构,并且结构与排序相关。我的大多数数据库中绝大多数字符采用基本拉丁编码,少数其他字符通常位于此处或此处的字段中。目前尚不清楚在这些情况下是否会有任何性能提升。会有吗?我很想在我的一些真实数据上运行它。
i
informatik01

This post 很好地描述了它。

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


如果您不关心正确性,那么使任何算法无限快都是微不足道的。只需使用 utf8_unicode_ci 并假装另一个不存在。
@tchrist,但如果您关心正确性和速度之间的某种平衡,utf8_general_ci 可能适合您
@tchrist 永远不要成为游戏程序员;)
@onassar - MySQL 8.0 声称显着提高了所有排序规则的性能。
D
Dana the Sane

请参阅 mysql 手册,Unicode Character Sets 部分:

对于任何 Unicode 字符集,使用 _general_ci 归类执行的操作都比使用 _unicode_ci 归类执行的操作快。例如,utf8_general_ci 排序规则的比较比 utf8_unicode_ci 的比较更快,但正确性稍差。原因是 utf8_unicode_ci 支持扩展等映射;也就是说,当一个字符比较等于其他字符的组合时。例如,在德语和其他一些语言中,“ß”等于“ss”。 utf8_unicode_ci 还支持缩写和可忽略的字符。 utf8_general_ci 是不支持扩展、收缩或可忽略字符的传统排序规则。它只能在字符之间进行一对一的比较。

总而言之, utf_general_ci 使用比应该实现整个标准的 utf_unicode_ci 更小且更不正确(根据标准)的比较集。 general_ci 集会更快,因为要做的计算更少。


没有“稍微不太正确”这样的东西。正确性是一个布尔特性;它不接受程度的修饰符。只需使用 utf8_unicode_ci 并假装错误的损坏版本不存在。
我在让 5.6.15 采用 collation_connection 设置时遇到了问题,结果你必须在 SET 行中传递它,比如“SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci”。感谢 Mathias Bynens 的解决方案,这是他非常有用的指南:mathiasbynens.be/notes/mysql-utf8mb4
@tchrist说正确性是布尔值的问题在于它没有考虑不依赖绝对正确性的情况。您的基本观点并非无效,我也不是试图拥护 general_ci 的好处,但是您关于正确性的一般性陈述很容易被证明是错误的。我在我的职业中每天都这样做。撇开喜剧不谈,斯图尔特有一个很好的观点here
通过地理定位或游戏开发,我们一直在用正确性与性能进行交易。当然,正确性是 01 之间的实数,而不是布尔值。 :) EG 在边界框中选择地理点是“附近点”的近似值,它不如计算点与参考点之间的距离并对其进行过滤。但是 both 都是近似值,事实上,完全正确几乎是无法实现的。请参阅 coastline paradoxIEEE 754
TL;DR:请提供为 1/3 打印正确结果的程序
s
simhumileco

简而言之:

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

但如果您对性能完全感兴趣 - 请使用 utf8_general_ci,但要知道它有点过时了。

性能方面的差异非常小。


两者现在都已过时 - 有关更多信息,请参阅已接受的答案
K
Kamil Kiełczewski

一些细节(PL)

正如我们所读到的 here (Peter Gulutzan),在排序/比较波兰字母“Ł”(带笔划的 L - html esc:Ł)(小写:“ł” - 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 之前。这种编码没有一个更好或更坏 - 这取决于您的需求。


A
Adam

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

排序:

utf8mb4_general_ci 删除所有重音符号并一一排序,这可能会产生错误的排序结果。

utf8mb4_unicode_ci 排序准确。

字符匹配

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

例如,在 utf8mb4_unicode_ci 中有 i != ı,但在 utf8mb4_general_ci 中有 ı=i

例如,假设您有一行包含 name="Yılmaz"。然后

select id from users where name='Yilmaz';

如果搭配是 utf8mb4_general_ci,将返回该行,但如果它与 utf8mb4_unicode_ci 搭配,它将返回该行!

另一方面,我们在 utf8mb4_unicode_ci 中有 a=ªß=ss,而在 utf8mb4_general_ci 中不是这种情况。所以假设你有一排有 name="ªßi",然后

select id from users where name='assi';

如果 collocation 为 utf8mb4_unicode_ci,则返回该行,但如果 collocation 设置为 utf8mb4_general_ci,则返回一行。

可以找到每个搭配的完整匹配列表 here


D
DavidH

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


同样重要的是要注意,与 MySQL 8.0 相关的分析观察到没有任何显着的好处。所以这个问题的答案似乎高度依赖于版本。