776

我想知道对于一个您不能 100% 确定将输入什么内容的一般网站,在 MySQL 中是否有“最佳”排序选择?我知道所有的编码都应该是相同的,例如 MySQL、Apache、HTML 和 PHP 中的任何东西。

过去我已将 PHP 设置为以“UTF-8”输出,但是这在 MySQL 中与哪个排序规则匹配?我认为它是 UTF-8 之一,但我使用过utf8_unicode_ci, utf8_general_ci, 和utf8_bin之前。

4

11 回答 11

659

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

utf8_general_ci比 快一些utf8_unicode_ci,但不太准确(用于排序)。特定语言的utf8 编码(例如utf8_swedish_ci)包含额外的语言规则,使它们对这些语言进行最准确的排序。大多数时候我使用utf8_unicode_ci(我更喜欢准确性而不是小的性能改进),除非我有充分的理由更喜欢特定的语言。

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

于 2008-12-15T07:58:27.243 回答
141

实际上,您可能想使用utf8_unicode_cior utf8_general_ci

  • utf8_general_ci通过去除所有重音符号并像 ASCII 一样排序
  • utf8_unicode_ci使用 Unicode 排序顺序,因此可以在更多语言中正确排序

但是,如果您只使用它来存储英文文本,那么这些应该没有什么不同。

于 2008-12-15T08:02:37.700 回答
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;
于 2010-06-13T11:02:40.043 回答
107

最好使用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 切换为 Barracuda
  • SQL CREATE 语句可能需要包括:ROW_FORMAT=DYNAMIC
    • VARCHAR(192) 和更大的索引需要 DYNAMIC。

注意:切换到BarracudafromAntelope可能需要多次重启 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)ifROW_FORMAT=DYNAMIC从 CREATE 语句中删除而生成的错误 #1709。

注意:将索引更改为限制为前 128 个字符可以contact消除使用 Barracuda 的要求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

参考:

于 2014-08-24T19:57:22.880 回答
47

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

charset unicode 文档中的示例:

utf8_general_ci对德语和法语也都满意,除了 'ß' 等于 's' 而不是 'ss'。如果这对您的应用程序是可以接受的,那么您应该使用 utf8_general_ci它,因为它更快。否则,使用它,utf8_unicode_ci因为它更准确。

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

于 2008-12-15T08:04:36.323 回答
22

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

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

在我的书中,“价值”永远不应该在一百万年内等于“价值”。

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

于 2010-12-07T01:42:37.667 回答
13

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

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

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

aka 它应该使搜索和索引数据更快/更有效/更有用。

于 2008-12-15T07:55:17.607 回答
12

公认的答案相当明确地建议使用 utf8_unicode_ci,虽然对于很棒的新项目,我想把我最近的相反经验联系起来,以防万一它节省了任何人的时间。

因为 utf8_general_ci 是 MySQL 中 Unicode 的默认排序规则,所以如果你想使用 utf8_unicode_ci 那么你最终不得不在很多地方指定它。

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

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

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

于 2013-07-30T13:20:02.873 回答
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”字符集的存储指定.

于 2012-05-08T13:27:45.163 回答
5

我发现这些整理图表很有帮助。http://collat​​ion-charts.org/mysql60/。我不确定哪个是使用的 utf8_general_ci。

例如,这里是 utf8_swedish_ci 的图表。它显示了它解释为相同的字符。http://collat​​ion-charts.org/mysql60/mysql604.utf8_swedish_ci.html

于 2015-04-12T12:34:37.283 回答
2

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

SET NAMES utf8;

你的问题应该得到解决。

于 2015-06-23T06:01:30.543 回答