MySQL 和 PHP 的最佳排序规则是什么?[关闭]
题
我想知道对于您不能 100% 确定将输入什么内容的一般网站,MySQL 中是否有排序规则的“最佳”选择?我知道所有编码都应该相同,例如 MySQL、Apache、HTML 和 PHP 中的任何内容。
过去我将 PHP 设置为以“UTF-8”输出,但是这与 MySQL 中的哪种排序规则匹配?我认为它是 UTF-8 之一,但我用过 utf8_unicode_ci
, utf8_general_ci
, , 和 utf8_bin
前。
解决方案
主要区别在于排序准确性(比较语言中的字符时)和性能。唯一特殊的是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
其他提示
要非常,非常注意这个问题,可以使用utf8_general_ci
时发生的。
MySQL不会在所选择的语句一些字符之间进行区分,如果使用utf8_general_ci
归类。这可能导致非常讨厌的错误 - 特别是例如,在用户名都参与其中。根据使用的数据库表的实现,这个问题可能允许恶意用户创建一个用户名相匹配的管理员帐户。
这个问题暴露本身至少是在早期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;
实际上,您可能想使用 utf8_unicode_ci
或者 utf8_general_ci
.
utf8_general_ci
通过去除所有重音符号并像 ASCII 一样进行排序来进行排序utf8_unicode_ci
使用 Unicode 排序顺序,因此可以在更多语言中正确排序
但是,如果您仅使用它来存储英文文本,那么它们应该没有什么不同。
最好使用字符集 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。
笔记:切换到 Barracuda
从 Antelope
, ,可能需要多次重新启动 MySQL 服务。 innodb_file_format_max
直到 MySQL 服务重新启动后才会更改为: innodb_file_format = barracuda
.
MySQL 使用旧的 Antelope
InnoDB 文件格式。 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;
- 您可以看到生成的错误#1709
INDEX contact_idx (contact)
如果ROW_FORMAT=DYNAMIC
从 CREATE 语句中删除。
笔记:更改索引以限制为前 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
参考:
排序规则如何影响数据的排序和如何将字符串相互比较。这意味着你应该使用大多数用户期望的排序规则。
utf8_general_ci
也令人满意 两个德国和法国,除了 该“SS”等于“S”,而不是 “SS”。如果这是你可以接受的 应用程序,那么你应该使用utf8_general_ci
,因为它更快。 否则,使用utf8_unicode_ci
因为 它是更精确的。
所以 - 这取决于你的预期的用户群,并在你需要多少的正确的排序。对于英语用户群,utf8_general_ci
应该足够了,其他语言,如瑞典,已建立特殊的排序规则。
从本质上讲,这取决于你是怎么想的字符串。
我总是使用,因为由希丁克突出的问题utf8_bin。在我看来,只要数据库应该关注的,一个字符串仍然只是一个字符串。的字符串是一个数字UTF-8字符的。一个字符的二进制表示为何还需要知道你使用的语言?通常情况下,人们将构建数据库与范围的多语言网站系统。这是使用UTF-8作为字符集的整点。我有点pureist的,但我认为错误的风险严重大于略占优势,你可能会得到索引。任何语言相关的规则应比DBMS一个更高的水平来实现。
在我的书“价值”不应该在一万年等于“值”。
如果我想存储的文本字段,并做不区分大小写的搜索,我将使用MYSQL字符串函数用PHP功能,例如LOWER()和PHP函数用strtolower()。
对于 UTF-8 文本信息,您应该使用 utf8_general_ci
因为...
utf8_bin
: :通过字符串中每个字符的二进制值比较字符串utf8_general_ci
: :使用通用语言规则比较字符串,并使用对案例不敏感的比较
又名它将使数据的搜索和索引更快/更高效/更有用。
接受的答案非常明确地表明,使用utf8_unicode_ci,并同时为新项目这是伟大的,我想涉及我最近的经验,相反,以防万一它保存任何一段时间。
由于utf8_general_ci是在MySQL对Unicode的默认排序规则,如果你想使用utf8_unicode_ci,那么你最终不得不在很多的地方可以指定它。
例如,所有客户端连接不仅具有默认的字符集(有意义的我),还包括默认排序规则(即,核对将总是默认utf8_general_ci对Unicode)。
有可能的,如果你使用utf8_unicode_ci你的领域,你的脚本连接到数据库需要进行更新,以明确提及所需的整理 - 使用文本字符串时,你的连接使用默认排序规则可能无法以其他方式查询
其结果是,任何尺寸的现有系统转换为Unicode / UTF8时,你可能最终被迫使用utf8_general_ci的因为MySQL处理默认的方式。
有关由希丁克突出的情况下,我会强烈建议使用任一utf8_unicode_cs(区分大小写,严格匹配,正确排序的大部分)代替utf8_bin(严格匹配,不正确的排序)。
如果该字段的目的是要被搜索,而不是为用户匹配,然后使用utf8_general_ci或utf8_unicode_ci。两者都是不区分大小写,一个将losely匹配(“SS”等于“S”,而不是“SS”)。也有语言特定版本,例如utf8_german_ci其中失去匹配更适合于所指定的语言
[编辑 - 近6年后]
我不再推荐“UTF8”字符在MySQL设定,而不是推荐“utf8mb4”字符集。它们匹配几乎完全,但允许一小(很多)更多的Unicode字符。
实际上,MySQL的应该已经更新了“UTF8”字符集和相应的归类,以匹配“UTF8”说明书中,而是一个独立的字符集和相应的归类,以不为那些已经在使用他们的不完整的“UTF8影响存储指定“字符集。
我发现这些整理图表很有帮助。 http://collation-charts.org/mysql60/. 。我不确定哪个是使用的 utf8_general_ci 。
例如,这里是 utf8_swedish_ci 的图表。它显示它解释为相同的字符。 http://collation-charts.org/mysql60/mysql604.utf8_swedish_ci.html
在你的数据库上传文件,任何行之前添加以下行:
SET NAMES utf8;
和你的问题应该得到解决。