PHP使用MySQL的最佳sorting规则是什么?
我想知道是否有一个“最好的”select整理MySQL的一般网站,你不是100%确定将被input? 我明白,所有的编码应该是相同的,如MySQL,Apache,HTML和PHP内的任何东西。
在过去,我已经设置PHP以“UTF-8”输出,但是这种整理是否在MySQL中匹配? 我认为这是UTF-8之一,但我以前使用过utf8_unicode_ci
, utf8_general_ci
和utf8_bin
。
主要区别在于sorting准确性(比较语言中的字符)和性能。 唯一特别的是用于比较二进制格式的字符的utf8_bin。
utf8_general_ci
比utf8_unicode_ci
快一些,但不太准确(用于sorting)。 特定的语言utf8编码 (如utf8_swedish_ci
)包含额外的语言规则,使他们对这些语言sorting最准确。 大多数情况下我使用utf8_unicode_ci
(我更喜欢精确性来改善性能),除非我有充分的理由select特定的语言。
你可以在MySQL手册上阅读更多关于特定的unicode字符集 – http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html
非常非常了解使用utf8_general_ci
时可能发生的这个问题。
如果使用utf8_general_ci
sorting规则,MySQL将不会区分select语句中的某些字符。 这可能会导致非常讨厌的错误 – 特别是在涉及用户名的情况下。 根据使用数据库表的实现,此问题可能允许恶意用户创build与pipe理员帐户匹配的用户名。
这个问题至less在早期的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
通过去除所有的重音和sorting,就好像它是ASCII一样 -
utf8_unicode_ci
使用Unicodesorting顺序,因此它在更多语言中正确sorting
但是,如果您只是使用这个来存储英文文本,这些不应该有所不同。
最好使用utf8mb4
归类的字符集utf8mb4_unicode_ci
。
字符集utf8
只支持less量的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
- DYNAMIC是VARCHAR(192)及更大的索引所必需的。
注意:从Antelope
切换到Barracuda
,可能需要多次重新启动MySQL服务。 innodb_file_format_max
不会改变,直到MySQL服务重新启动后: innodb_file_format = barracuda
。
MySQL使用旧的Antelope
InnoDB文件格式。 Barracuda
支持dynamic行格式,如果您不想在切换到字符集之后创build索引和键时遇到SQL错误,您将需要这些格式: utf8mb4
- #1709 – 索引列的大小太大。 最大列大小是767字节。
- #1071 – 指定的密钥太长; 最大密钥长度是767字节
以下场景已经在MySQL 5.6.17上进行了testing:默认情况下,MySQL是这样configuration的:
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语句中删除了
ROW_FORMAT=DYNAMIC
则可以看到为INDEX contact_idx (contact)
生成的错误#1709。
注意:将索引更改为限制为contact
的前128个字符,无需使用ROW_FORMAT=DYNAMIC
Barracuda
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 +--------------------+---------------------+
在您的数据库适配器中,您可能需要为连接设置字符集和sorting规则:
SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'
在PHP中,这将被设置为: \PDO::MYSQL_ATTR_INIT_COMMAND
参考文献:
- Mysql 5.6参考手册:InnoDB表的限制
- 如何在MySQL数据库中支持完整的Unicode
sorting规则会影响数据的sorting方式以及string如何相互比较。 这意味着您应该使用大多数用户期望的sorting规则。
来自文档的示例:
除了“ß”等于“s”,而不是“ss”之外,
utf8_general_ci
对于德语和法语都是令人满意的。 如果你的应用程序可以接受,那么你应该使用utf8_general_ci
因为它更快。 否则,使用utf8_unicode_ci
是因为它更准确。
所以 – 这取决于您的预期用户群和多less你需要正确的sorting。 对于英文用户群, utf8_general_ci
应该足够了,对于其他语言,如瑞典语,已经创build了特殊的归类。
基本上,这取决于你如何看待一个string。
由于Guus突出显示的问题,我总是使用utf8_bin。 在我看来,就数据库而言,string依然只是一个string。 一个string是一些UTF-8字符。 一个字符有二进制表示,所以为什么它需要知道你正在使用的语言? 通常,人们将为具有多语种站点的系统构build数据库。 这是使用UTF-8作为字符集的重点。 我是一个纯粹主义者,但我认为这个bug的风险大大超过了你在索引方面可能带来的轻微优势。 任何语言相关的规则都应该在比DBMS高得多的水平上完成。
在我的书中,“价值”不应该在一百万年内等于“valúe”。
如果我想存储一个文本字段并进行不区分大小写的search,我将使用PHP函数(如LOWER()和php函数strtolower())的MYSQLstring函数。
对于UTF-8文本信息,您应该使用utf8_general_ci
因为…
-
utf8_bin
:通过string中每个字符的二进制值比较string -
utf8_general_ci
:使用通用语言规则比较string,并使用不区分大小写的比较
也就是说它将会使数据的search和索引更快/更高效/更有用。
被接受的答案相当明确地暗示了使用utf8_unicode_ci,而对于新的项目来说,这很好,我想把我最近的相反的经验,以防万一节省时间。
因为utf8_general_ci是MySQL中默认的Unicodesorting规则,所以如果你想使用utf8_unicode_ci,那么你最终不得不在很多地方指定它。
例如,所有的客户端连接不仅有一个默认的字符集(对我来说是有意义的),而且还有一个默认的sorting规则(即sorting总是默认为Unicode的utf8_general_ci)。
很有可能,如果你的字段使用utf8_unicode_ci,那么连接到数据库的脚本将需要更新,以明确地提到所需的sorting规则 – 否则,当连接使用默认sorting规则时,使用文本string的查询可能会失败。
结果是,在将任何大小的现有系统转换为Unicode / UTF8时,由于MySQL处理默认值的方式,最终可能会被迫使用utf8_general_ci。
对于Guus强调的情况,我强烈build议使用utf8_unicode_cs(区分大小写,严格匹配,大部分正确sorting)而不是utf8_bin(严格匹配,不正确的sorting)。
如果该字段用于search,而不是匹配用户,则使用utf8_general_ci或utf8_unicode_ci。 两者都是不区分大小写的,一个会失败匹配('ß'等于's',而不是'ss')。 还有语言特定的版本,如utf8_german_ci,其中丢失匹配更适合于指定的语言。
我发现这些sorting图有帮助。 http://collation-charts.org/mysql60/ 。 我不知道哪个是使用utf8_general_ci虽然。
例如,这里是utf8_swedish_ci的图表。 它显示了它解释为相同的字符。 http://collation-charts.org/mysql60/mysql604.utf8_swedish_ci.html
在您的数据库上传文件中,在任何一行之前添加followin行:
SET NAMES utf8;
而你的问题应该解决。
对于casee字段是为了search,而不是为用户匹配,那么使用utf8_general_ce字段是为了search,而不是为用户匹配,然后使用utf8_general_ci或utf8_unicode_ci。 两者都是不区分大小写的,一个会失败匹配('ß'等于's',而不是'ss')。 还有语言特定的版本,如utf8_german_ci,其中丢失匹配更适合于指定的语言.i或utf8_unicode_ci。 两者都是不区分大小写的,一个会失败匹配('ß'等于's',而不是'ss')。 还有语言特定的版本,如utf8_german_ci,其中丢失匹配更适合于指定的语言。 由Guus强调,我强烈build议使用utf8_unicode_cs(区分大小写,严格匹配,命令ce字段旨在search,而不是匹配的用户,然后使用utf8_general_ci或utf8_unicode_ci。两者都是不区分大小写,一个会失败匹配('ß'等于's',而不是'ss')。还有语言特定的版本,比如utf8_german_ci,其中丢失匹配更适合指定的语言,大部分是正确的)而不是utf8_bin (严格匹配,不正确的sorting)。
如果该字段用于search,而不是匹配用户,则使用utf8_general_ci或utf8_unicode_ci。 两者都是不区分大小写的,一个会失败匹配('ß'等于's',而不是'ss')。 还有语言特定的版本,如utf8_german_ci,其中丢失匹配更适合于指定的语言。