在 utf8_general_ci
和 utf8_unicode_ci
之间,在性能方面有什么不同吗?
utf8[mb4]_unicode_ci
,您可能会更喜欢 utf8[mb4]_unicode_520_ci
。
utf8mb4_0900_ai_ci
。
对于那些在 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)。区分大小写的排序会导致一些奇怪的结果,区分大小写的比较可能会导致重复值仅在字母大小写上有所不同,因此区分大小写的排序规则对文本数据不受欢迎 - 如果大小写对您很重要,那么标点符号就可以忽略等等可能也很重要,二进制排序规则可能更合适。
接下来,unicode
或 general
指的是特定的排序和比较规则 - 特别是文本标准化或比较的方式。 utf8mb4 字符编码有许多不同的规则集,其中 unicode
和 general
是两个试图在所有可能的语言而不是一种特定的语言中都能正常工作的规则。这两组规则之间的差异是此答案的主题。请注意,unicode
使用 Unicode 4.0 中的规则。 MySQL 和 MariaDB 的最新版本使用 Unicode 5.2 中的规则添加了规则集 unicode_520
,而 MySQL 8.x 使用 Unicode 9.0 中的规则添加了 0900
(删除了“unicode_”部分)。
最后,utf8mb4
当然是内部使用的字符编码。在这个答案中,我只谈论基于 Unicode 的编码。
我想知道使用 utf8_general_ci
和 utf8_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
然后我创建了以下存储过程来对简单的 SELECT
、SELECT
与 LIKE
和排序(SELECT
与 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 毫秒
使用 utf8_unicode_ci:10,271 毫秒
在这个基准测试中,使用 utf8_unicode_ci
比 utf8_general_ci
慢 3.2%。
benchmark_select_like()
使用 utf8_general_ci:11,441 毫秒
使用 utf8_unicode_ci:12,811 毫秒
在这个基准测试中,使用 utf8_unicode_ci
比 utf8_general_ci
慢 12%。
benchmark_order_by()
使用 utf8_general_ci:11,944 毫秒
使用 utf8_unicode_ci:12,887 毫秒
在此基准测试中,使用 utf8_unicode_ci
比 utf8_general_ci
慢 7.9%。
utf8_general_ci
的性能提升太小了,不值得使用。
CONV(FLOOR(RAND() * 99999999999999), 20, 36)
只生成 ASCII,并且没有 Unicode 字符由排序规则算法处理。 2) Description = 'test' COLLATE ...
和 Description LIKE 'test%' COLLATE ...
在运行时只处理一个字符串(“test”),不是吗? 3)在实际应用程序中,排序中使用的列可能会被索引,并且不同排序规则与真正的非 ASCII 文本的索引速度可能会有所不同。
This post 很好地描述了它。
简而言之:utf8_unicode_ci
使用 Unicode 标准中定义的 Unicode 排序算法,而 utf8_general_ci
是一种更简单的排序顺序,导致排序结果“不太准确”。
utf8_unicode_ci
并假装另一个不存在。
utf8_general_ci
可能适合您
请参阅 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
并假装错误的损坏版本不存在。
0
和 1
之间的实数,而不是布尔值。 :) EG 在边界框中选择地理点是“附近点”的近似值,它不如计算点与参考点之间的距离并对其进行过滤。但是 both 都是近似值,事实上,完全正确几乎是无法实现的。请参阅 coastline paradox 和 IEEE 754
1/3
打印正确结果的程序
简而言之:
如果您需要更好的排序顺序 - 使用 utf8_unicode_ci
(这是首选方法),
但如果您对性能完全感兴趣 - 请使用 utf8_general_ci
,但要知道它有点过时了。
性能方面的差异非常小。
一些细节(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
之前。这种编码没有一个更好或更坏 - 这取决于您的需求。
排序和字符匹配有两个很大的区别:
排序:
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。
根据这篇文章,当使用 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/
utf8_general_ci
:它根本行不通。这是 50 年前 ASCII 古怪的糟糕日子的倒退。如果没有来自 UCD 的 foldcase 映射,则无法完成 Unicode 不区分大小写的匹配。例如,“Σίσυφος”中有三个不同的 sigma;或者“TSCHüẞ”的小写字母是“tschüβ”,而“tschüβ”的大写字母是“TSCHÜSS”。你可以是对的,或者你可以很快。因此你必须使用utf8_unicode_ci
,因为如果你不关心正确性,那么让它无限快是微不足道的。