搭配MySQL与MySQL搭配使用的最佳排序规则是什么?[关闭]


731

我想知道对于一般网站而言,MySQL中是否存在“最佳”整理规则的选择,而您不确定100%确定将输入什么内容吗?我知道所有编码都应该相同,例如MySQL,Apache,HTML和PHP中的任何内容。

过去,我已将PHP设置为以“ UTF-8”输出,但是在MySQL中此匹配哪种排序规则?我想它是UTF-8的人之一,但我已经使用utf8_unicode_ciutf8_general_ciutf8_bin之前。


35
旁注:MySQL的“ utf8”不是正确的UTF-8(不支持𝌆等4+字节Unicode字符),但是“ utf8mb4”是正确的。使用utf8时,插入时将从第一个不受支持的Unicode字符开始将字段截断。mathiasbynens.be/notes/mysql-utf8mb4
basic6

6
我不知道我们是否会永远需要所有这些表情符号,5个字节...... 叹息
阿尔瓦罗·冈萨雷斯

1
相关问题:stackoverflow.com/questions/38228335/… “哪个MySQL排序规则与PHP的字符串比较完全匹配?”
William Entriken '16

Answers:


617

主要区别是排序准确性(在比较语言中的字符时)和性能。唯一的特殊之处是utf8_bin,它用于比较二进制格式的字符。

utf8_general_ci比稍快一些utf8_unicode_ci,但准确性(排序)较差。在具体的语言UTF8编码(如utf8_swedish_ci)包含其他语言的规则,使他们成为最准确的排序这些语言。utf8_unicode_ci除非我有充分的理由偏爱特定的语言,否则我通常会使用大多数时间(我宁愿准确性而不是对性能的小改进)。

您可以在MySQL手册(http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html)上阅读有关特定Unicode字符集的更多信息。


4
性能改善不大?你确定吗 ?publib.boulder.ibm.com/infocenter/db2luw/v9r5/index.jsp?topic=/…您选择的排序规则会严重影响数据库中查询的性能。
亚当·拉玛丹

62
这是针对DB2而不是MySQL。另外,由于没有具体的数字或基准,因此您只是基于作者的意见。
伊兰·加珀林

3
请注意,如果您想使用函数,则在MySQL中存在一个错误(最新发行的版本),该函数始终使用utf8_general_ci返回字符串,如果您对字符串使用其他排序规则,则会引起问题-请参见bugs.mysql.com/ bug.php?id = 24690
El

1
根据我在不同语言环境中的经验,我会一直使用utf8_unicode_*
Shiplu Mokaddim

11
更新:对于较新的版本,建议utf8mb4utf8mb4_unicode_520_ci。这些可以为您提供其余的中文,以及改进的排序规则。
瑞克·詹姆斯

128

实际上,您可能想要使用utf8_unicode_ciutf8_general_ci

  • utf8_general_ci 通过剥离所有重音进行排序并进行排序,就好像它是ASCII一样
  • utf8_unicode_ci 使用Unicode排序顺序,因此可以在更多语言中正确排序

但是,如果仅使用它来存储英文文本,则它们应该没有什么不同。


1
我喜欢你的解释!好一个 但是我需要更好地了解为什么Unicode排序顺序比剥离重音更好的正确排序方式。
weia设计

14
@Adam确实取决于您的目标受众。要正确定位,排序是一个棘手的问题。例如,挪威语中的字母ÆØÅ是字母的后3个字母。使用utf8_general_ci,可将Ø和Å转换为O和A,从而在排序时将它们置于完全错误的位置(我不确定Æ的处理方式,因为is是连字而不是带重音的字符)。这种排序顺序在几乎所有语言中都是不同的,例如,挪威语和瑞典语具有不同的顺序(并且字母也略有不同,它们被认为是相等的):ÆØÅ排序为ÅØØ(实际字母为ÅÄÖ)。Unicode可以解决此问题。
Vegard Larsen 2013年

因此,我基本上要说的是,如果可以的话,应该使用特定于语言的排序,但是在大多数情况下,这是不可行的,因此请使用Unicode常规排序。它在某些语言中仍然会很奇怪,但是比ASCII更正确。
Vegard Larsen

3
@Manatax-对于任何utf8_归类,数据都将存储为utf8。排序规则是关于哪些字符被视为相等以及如何排序。
frymaster

2
@frymaster-不正确,如下:mathiasbynens.be/notes/mysql-utf8mb4 “ MySQL的utf8只允许您存储所有可能的Unicode代码点的5.88%”
数据

120

请非常注意使用时可能发生的此问题utf8_general_ci

如果使用utf8_general_ci排序规则,MySQL不会区分select语句中的某些字符。这可能会导致非常令人讨厌的错误,尤其是涉及用户名的错误。根据使用数据库表的实现,此问题可能允许恶意用户创建与管理员帐户匹配的用户名。

此问题至少在5.x早期版本中暴露出来-我不确定此行为是否稍后会更改。

我不是DBA,但是为了避免出现此问题,我总是选择utf8-bin不使用不区分大小写的格式。

下面的脚本通过示例描述了该问题。

-- first, create a sandbox to play in
CREATE DATABASE `sandbox`;
use `sandbox`;

-- next, make sure that your client connection is of the same 
-- character/collate type as the one we're going to test next:
charset utf8 collate utf8_general_ci

-- now, create the table and fill it with values
CREATE TABLE `test` (`key` VARCHAR(16), `value` VARCHAR(16) )
    CHARACTER SET utf8 COLLATE utf8_general_ci;

INSERT INTO `test` VALUES ('Key ONE', 'value'), ('Key TWO', 'valúe');

-- (verify)
SELECT * FROM `test`;

-- now, expose the problem/bug:
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get BOTH keys here! MySQLs UTF8 collates that are 
-- case insensitive (ending with _ci) do not distinguish between 
-- both values!
--
-- collate 'utf8_bin' doesn't have this problem, as I'll show next:
--

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get just one key now, as you'd expect.
--
-- This problem appears to be specific to utf8. Next, I'll try to 
-- do the same with the 'latin1' charset:
--

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_general_ci

-- next, convert the values that we've previously inserted
-- in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_general_ci;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected). This shows 
-- that the problem with utf8/utf8_generic_ci isn't present 
-- in latin1/latin1_general_ci
--
-- To complete the example, I'll check with the binary collate
-- of latin1 as well:

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected).
--
-- Finally, I'll re-introduce the problem in the exact same 
-- way (for any sceptics out there):

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_generic_ci

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

-- now, re-check for the problem/bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Two keys.
--

DROP DATABASE sandbox;

36
-1:通过对相关列应用唯一键,可以肯定地解决此问题。如果两个值分别为'value'和,您将看到相同的行为'valUe'。排序规则的全部要点是,它为(当其他情况中)当两个字符串被认为彼此相等时提供了规则。
Hammerite

13
这正是我要说明的问题-排序规则使两件事相等,而实际上却根本不打算相等(因此,唯一约束与您要达到的目的恰好相反)
Guus

18
但是您将其描述为“问题”,并且当行为正是排序规则要实现的目的时,就会导致“错误”。您的描述是正确的,但选择不适当的排序规则只是DBA的错误。
Hammerite

32
问题是,当您输入两个用户名时,排序规则认为它们是相等的,如果您将列用户名设置为唯一,那将是不允许的,您当然应该这样做!
霍格沃茨的学生

12
我同时支持此答案和@Hammerite的评论,因为它们两者的结合有助于我理解整理。
Nacht-恢复莫妮卡2015年

86

最好将字符集utf8mb4与排序规则一起使用utf8mb4_unicode_ci

字符集,utf8仅支持少量UTF-8代码点,大约占可能字符的6%。utf8仅支持基本多语言平面(BMP)。还有其他16架飞机。每个平面包含65,536个字符。utf8mb4支持所有17架飞机。

MySQL将截断4个字节的UTF-8字符,从而导致数据损坏。

utf8mb4字符集于2010年3月24日在MySQL 5.5.3中引入。

使用新字符集所需进行的某些更改并非微不足道:

  • 您的应用程序数据库适配器中可能需要进行更改。
  • 需要对my.cnf进行更改,包括设置字符集,排序规则以及将innodb_file_format切换为梭子鱼
  • SQL CREATE语句可能需要包括: ROW_FORMAT=DYNAMIC
    • 对于VARCHAR(192)和更大的索引,需要DYNAMIC。

注意:Barracuda从切换到Antelope,可能需要多次重启MySQL服务。innodb_file_format_max在MySQL服务重新启动到之前,不会更改innodb_file_format = barracuda

MySQL使用旧的AntelopeInnoDB文件格式。Barracuda支持动态行格式,如果您不希望在切换到字符集后遇到用于创建索引和键的SQL错误,则需要使用以下格式:utf8mb4

  • #1709-索引列大小太大。最大列大小为767个字节。
  • #1071-指定的密钥太长;最大密钥长度为767字节

以下场景已在MySQL 5.6.17上进行了测试:默认情况下,MySQL的配置如下:

SHOW VARIABLES;

innodb_large_prefix = OFF
innodb_file_format = Antelope

停止MySQL服务,并将选项添加到现有的my.cnf中:

[client]
default-character-set= utf8mb4

[mysqld]
explicit_defaults_for_timestamp = true
innodb_large_prefix = true
innodb_file_format = barracuda
innodb_file_format_max = barracuda
innodb_file_per_table = true

# Character collation
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci

示例SQL CREATE语句:

CREATE TABLE Contacts (
 id INT AUTO_INCREMENT NOT NULL,
 ownerId INT DEFAULT NULL,
 created timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
 modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 contact VARCHAR(640) NOT NULL,
 prefix VARCHAR(128) NOT NULL,
 first VARCHAR(128) NOT NULL,
 middle VARCHAR(128) NOT NULL,
 last VARCHAR(128) NOT NULL,
 suffix VARCHAR(128) NOT NULL,
 notes MEDIUMTEXT NOT NULL,
 INDEX IDX_CA367725E05EFD25 (ownerId),
 INDEX created (created),
 INDEX modified_idx (modified),
 INDEX contact_idx (contact),
 PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT=DYNAMIC;
  • 您会看到为INDEX contact_idx (contact)if 生成的错误#1709 ROW_FORMAT=DYNAMIC已从CREATE语句中删除。

注意:将索引更改为限制为前128个字符,就contact不再需要将梭子鱼与ROW_FORMAT=DYNAMIC

INDEX contact_idx (contact(128)),

另请注意:当说字段的大小为时VARCHAR(128),不是128字节。您可以使用128个4字节字符或128个1字节字符。

INSERT语句应在第2行中包含4个字节的“ poo”字符:

INSERT INTO `Contacts` (`id`, `ownerId`, `created`, `modified`, `contact`, `prefix`, `first`, `middle`, `last`, `suffix`, `notes`) VALUES
(1, NULL, '0000-00-00 00:00:00', '2014-08-25 03:00:36', '1234567890', '12345678901234567890', '1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678', '', ''),
(2, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '', ''),
(3, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '123💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '', '');

您可以看到该last列使用的空间量:

mysql> SELECT BIT_LENGTH(`last`), CHAR_LENGTH(`last`) FROM `Contacts`;
+--------------------+---------------------+
| BIT_LENGTH(`last`) | CHAR_LENGTH(`last`) |
+--------------------+---------------------+
|               1024 |                 128 | -- All characters are ASCII
|               4096 |                 128 | -- All characters are 4 bytes
|               4024 |                 128 | -- 3 characters are ASCII, 125 are 4 bytes
+--------------------+---------------------+

在数据库适配器中,您可能需要为连接设置字符集和排序规则:

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

在PHP中,这将设置为: \PDO::MYSQL_ATTR_INIT_COMMAND

参考文献:




6
utf8mb4_unicode_ci应该绝对是2015
Trevor Gehman

7
更新... utf8mb4_unicode_520_ci更好。将来,utf8mb4_unicode_800_ci随着MySQL赶上Unicode标准,将会出现(或类似的东西)。
里克·詹姆斯

46

排序规则会影响数据的排序方式以及字符串之间的比较方式。这意味着您应该使用大多数用户期望的排序规则。

charset unicode文档中的示例:

utf8_general_ci除“ß”等于“ s”而不是“ ss”外,德语和法语也令人满意。如果这对于您的应用程序是可接受的,则应使用utf8_general_ci它, 因为它速度更快。否则,请使用utf8_unicode_ci它,因为它更准确。

因此-这取决于您的预期用户群以及需要多少正确排序。对于英语用户群,utf8_general_ci应该足够,对于其他语言(如瑞典语),已经创建了特殊的排序规则。


1
我使用utf8_general_ci,花了一对夫妇秒,而排序和armscii_general_ci做到了极其这个quick.Why发生了一个问题,你觉得哪个归类所使用的社交网站?

22

本质上,这取决于您如何看待字符串。

由于Guus强调的问题,我始终使用utf8_bin。我认为,就数据库而言,字符串仍然只是字符串。字符串是许多UTF-8字符。字符具有二进制表示形式,为什么它需要知道您使用的语言?通常,人们将为具有多语言站点范围的系统构建数据库。这就是使用UTF-8作为字符集的全部要点。我有点纯粹,但是我认为该错误的风险大大超过了建立索引的轻微优势。任何与语言相关的规则都应在比DBMS更高的级别上完成。

在我的书中,“价值”在百万年之内绝不应等于“价值”。

如果我想存储文本字段并进行不区分大小写的搜索,则将MYSQL字符串函数与PHP函数(例如LOWER()和php函数strtolower())一起使用。


9
如果您希望对字符串进行二进制比较,那么您当然应该使用二进制排序规则;但是将替代排序规则视为“错误风险”或仅仅为了索引方便而认为这表明您不完全了解排序规则的要点。
Hammerite

13

对于UTF-8文本信息,应使用utf8_general_ci... ,因为...

  • utf8_bin:通过字符串中每个字符的二进制值比较字符串

  • utf8_general_ci:使用通用语言规则和不区分大小写的比较来比较字符串

也就是它将使搜索和索引数据更快/更有效/更有用。


12

公认的答案很明确地建议使用utf8_unicode_ci,尽管对于很棒的新项目,我想结合一下我最近的相反经验,以防万一可以节省一些时间。

由于utf8_general_ci是MySQL中Unicode的默认排序规则,因此,如果要使用utf8_unicode_ci,则最终必须在很多地方指定它。

例如,所有客户端连接不仅具有默认字符集(对我而言很有意义),而且还具有默认排序规则(即,对于unicode,该排序规则将始终默认为utf8_general_ci)。

可能的是,如果您对字段使用utf8_unicode_ci,则需要更新连接到数据库的脚本以明确提及所需的排序规则-否则,当您的连接使用默认排序规则时,使用文本字符串的查询可能会失败。

结果是,在将任何大小的现有系统转换为Unicode / utf8时,由于MySQL处理默认值的方式,您可能最终不得不使用utf8_general_ci。


8

对于Guus强调的情况,我强烈建议使用utf8_unicode_cs(区分大小写,严格匹配,在大多数情况下正确排序),而不是utf8_bin(严格匹配,错误排序)。

如果要搜索该字段(而不是针对用户匹配),请使用utf8_general_ci或utf8_unicode_ci。两者都不区分大小写,一个将失败匹配(“ß”等于“ s”,而不是“ ss”)。还有一些特定于语言的版本,例如utf8_german_ci,其中,丢失匹配更适合于指定的语言。

[编辑-近6年后]

我不再推荐在MySQL上使用“ utf8”字符集,而是推荐使用“ utf8mb4”字符集。它们几乎完全匹配,但允许更多(更多)Unicode字符。

实际上,MySQL应该已经更新了“ utf8”字符集和相应的排序规则以匹配“ utf8”规范,但是相反,一个单独的字符集和相应的排序规则不会影响已经使用其不完整的“ utf8”字符集的存储名称。


5
仅供参考:utf8_unicode_cs不存在。唯一区分大小写的utf8是utf8_bin。问题是utf8_bin排序不正确。参见:stackoverflow.com/questions/15218077/…–
Costa

1
感谢您的更新!
普罗米修斯


2

在您的数据库上传文件中,在任何行之前添加以下行:

SET NAMES utf8;

您的问题应该得到解决。


2
读一个问题:过去我已将PHP设置为在“ UTF-8”中输出,但是在MySQL中此匹配哪种排序规则?我以为它是UTF-8之一,但是我之前使用过utf8_unicode_ci,utf8_general_ci和utf8_bin。
Jitesh Sojitra,

5
这个答案与问题无关。此外,SET NAMES直接发出查询不会使客户端知道编码,并且可能以非常微妙的方式破坏某些功能,例如准备好的语句。
阿尔瓦罗·冈萨雷斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.