使用PHP的MySQL最好的排序规则是什么?

时间:2008-12-15 07:48:37

标签: php mysql encoding collation

我想知道对于一般网站,你是否有一个“最佳”的校对选择,你不是100%肯定会输入什么?我知道所有编码应该是相同的,例如MySQL,Apache,HTML和PHP中的任何内容。

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

11 个答案:

答案 0 :(得分:580)

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

utf8_general_ciutf8_unicode_ci略快,但不太准确(排序)。 特定语言utf8编码(例如utf8_swedish_ci)包含其他语言规则,使其最准确地对这些语言进行排序。大多数时候我使用utf8_unicode_ci(我更喜欢精确到小的性能改进),除非我有充分的理由选择特定的语言。

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

答案 1 :(得分:114)

非常,非常了解使用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;

答案 2 :(得分:112)

实际上,您可能希望使用utf8_unicode_ciutf8_general_ci

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

但是,如果您仅使用它来存储英文文本,则这些文本不应有所不同。

答案 3 :(得分:78)

最好将字符集utf8mb4与整理utf8mb4_unicode_ci一起使用。

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

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

{03}在2010年3月24日的MySQL 5.5.3中引入了utf8mb4字符集。

使用新字符集所做的一些必要更改并非易事:

  • 可能需要在应用程序数据库适配器中进行更改。
  • 需要对my.cnf进行更改,包括设置字符集,整理并将innodb_file_format切换为Barracuda
  • SQL CREATE语句可能需要包括:ROW_FORMAT=DYNAMIC
    • VARCHAR(192)及更大版本的索引需要DYNAMIC。

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

MySQL使用旧的Antelope InnoDB文件格式。 Barracuda支持动态行格式,如果您不想在切换到charset后遇到创建索引和键的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;
  • 如果从CREATE语句中删除INDEX contact_idx (contact),您可以看到为ROW_FORMAT=DYNAMIC生成的错误#1709。

注意:将索引更改为限制为contact上的前128个字符,无需使用梭子鱼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
+--------------------+---------------------+

在数据库适配器中,您可能需要为连接设置charset和collat​​ion:

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

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

参考文献:

答案 4 :(得分:43)

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

documentation的示例:

  

utf8_general_ci也令人满意   德语和法语,除了   'ß'等于's',而不是's'   “SS”。如果这对你来说是可以接受的   应用程序,然后你应该使用   utf8_general_ci因为它更快。   否则,请使用utf8_unicode_ci因为   它更准确。

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

答案 5 :(得分:22)

基本上,这取决于你对字符串的看法。

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

在我的书中,“价值”绝不应该在一百万年内等于“valúe”。

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

答案 6 :(得分:12)

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

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

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

a.k.a。它将使搜索和索引数据更快/更有效/更有用。

答案 7 :(得分:11)

接受的答案相当明确地建议使用utf8_unicode_ci,而对于那些很棒的新项目,我想提及我最近的相反经验,以防它节省了一些时间。

因为utf8_general_ci是MySQL中Unicode的默认排序规则,如果你想使用utf8_unicode_ci,那么你最终必须在 lot 的地方指定它。

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

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

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

答案 8 :(得分:8)

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

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

[编辑 - 近6年后]

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

实际上,MySQL应该更新“utf8”字符集和相应的排序规则以匹配“utf8”规范,而是单独的字符集和各自的排序规则,以便不影响那些已经使用其不完整的“utf8”的存储指定“字符集。

答案 9 :(得分:5)

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

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

答案 10 :(得分:2)

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

SET NAMES utf8;

你的问题应该解决了。