如何使用PHP在一个独特的MySQL列中存储UTF-8电子邮件地址?

时间:2016-08-06 17:15:20

标签: php mysql string encoding utf-8

我试图将UTF-8字符支持到电子邮件地址中。如果我理解正确,电子邮件地址仅限254 usable (ASCII) characters。基于此,我想将电子邮件地址存储在VARCHAR(254)ASCII MySQL InnoDB列中。我遇到的一个问题是验证这种情况。我试图将UTF-8转换为ASCII但是获得了如下所示的混合结果(我知道该示例不是有效的电子邮件,但我可以使用其他字符 - 这只是为了解释问题):

<?php
$string = '@.';
echo 'UTF-8 Value: ' . $string . '<br/>';
echo 'ASCII Length (from UTF-8 string):' . mb_strlen($string, 'ASCII') . '<br/>';
$stringAscii =  mb_convert_encoding($string, 'ASCII', 'UTF-8');
echo 'ASCII Length:' . strlen($stringAscii) . '<br/>';
echo 'ASCII Value:' . $stringAscii . '<br/>';

输出结果为:

  

UTF-8价值:@。

     

ASCII长度(来自UTF-8字符串):: 14

     

ASCII长度:5

     

ASCII值:?@?。

我希望ASCII字符串转换后长度为14个字符?如何将UTF-8字符串转换为ASCII而不会丢失其原始长度和值?基本上我正在寻找一种方法将UTF-8字符串存储为ASCII格式,同时能够将其转换回原始的UTF-8格式。

我还尝试过其他类型的编码输出(例如字节输出),但无法找到任何匹配14个字符长度的输出。我还尝试了iconv,它返回了字符的异常。以ASCII格式转换的想法是我可以在我的VARCHAR(254)中将此值作为MySQL中表的主键支持。我总是可以尝试转换为HTML-ENTITIES,但很难预测字符串的最大大小以在数据库模式中反映它。

另一种选择是在MySQL中使用UTF-8MB4编码的VARCHAR(256)列,但当用作主键时,这将超过767字节的索引限制并要求在InnoDB中启用大型索引,我更喜欢避免。

有没有办法实现我在MySQL中不使用innodb_large_prefix=on时尝试做的事情?

3 个答案:

答案 0 :(得分:4)

尼古拉斯,你似乎对你的问题中的Ascii Vs UTF-8字符集有一些基本的混淆,以及你的回答评论。

  
    

UTF-8值:@。

         

ASCII长度(来自UTF-8字符串):14

         

ASCII长度:5

         

ASCII值:?@?。

  
     

我希望ASCII字符串转换后长度为14个字符?

不,如果Panda Face UTF-8字符在Ascii中有代表,它将如何表示?充其量这将是主观的,例如<3B-)等。

没有Pandaface的翻译,因此它在输出字符集中被占位符?替换。这有点像试图拼写国王,但只有元音。 ascii选项比UTF8少。

所以请注意,Ascii是UTF-8的实用子集,反之亦然。

MySQL唯一存储解决方案

MYSQL唯一索引总共有767个字节的限制。您可以将这些索引链接在一起,对于任何表,MySQL都可以提供3072字节的唯一索引。为了使用排序规则UTF8mb4_unicode_ci的单个索引列(即您应该使用的那个),那么唯一索引列将是:

<max index size in bytes> / <max bytes per character in collation> 
          767             /            4                    = 191 characters. 

因此MySQL只会对任何UTF-8字符串的前191个字符进行无条件索引。

要回避此限制器,您将创建一个新表,其中包含两列,Auto_increment整数列和varchar列:

CREATE TABLE `emails` (
 `id` int(8) NOT NULL AUTO_INCREMENT,
 `email` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
 PRIMARY KEY (`id`),
 KEY `email` (`email`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

然后,每次添加新电子邮件地址时,如果该表已存在(该列已编入索引但不是unique),则搜索此表,如果不存在,则插入电子邮件地址并由{引用{1}}列。

id列始终为UTF8mb4,因为与MySQL标准email排序规则不同,这是完整的UTF8。 MySQL不能像你所说的那样唯一地限定大于767字节的数据,但是如果你的各种其他表引用了电子邮件的id行,那么其他表上的那列就是唯一的。

一些进一步的想法

1 utf8_不是一种有效的解决方案,因为对于任何字符,其实体的大小总是更大,请使用htmlentities字符,即{ {1}}这已经是4个字符长最好的情况,即使这些字符中的每一个都可以存储在&#34; 1字节&#34;中,这仍然是一个比>作为最坏情况的一般UTF-8字符将是4字节。

&gt;只会影响具有指定HTML替代品的字符,并且我不确定>htmlentities之类的内容是否具有敏感性(?)。

2 您看过甚至曾经使用过的最长的电子邮件地址是什么,这是一个真正的真实地址?虽然电子邮件地址的最大大小为254个ascii字符,但是:

<PandaFace>

现在查看该代码,这是根据定义允许的最长的ascii电子邮件地址。那&#39;很长,虽然不是不可能,但拥有这个长度的电子邮件地址(在ascii中)的用户数量将是一个极端的边缘。

在此行中向下迈出一步,假设您有一个64位UTF-8 4字节字符的电子邮件地址,因为您已将其设置为上限utf-8,

所以ascii这个长度的东西:

<shitpoo>

但是,作为UTF-8 4字节字符并将上述电子邮件翻译成某些UTF-8中文字符集,此电子邮件地址长度仍然是人类实际使用的实际上限范围,并具有地址。但它并不完全在公园外,除非你瞄准特定的市场观众,否则它不太可能。

767字节的MySQL唯一索引将限制为大约191个4字节UTF-8字符,然后您将在限制为2的电子邮件地址中限制为47个完全UTF-8字符(嗯,最多3个字符) )非UTF-8 4字节字符(例如thisisaverylongandtediousemailaddresswhichisprettyimpractical. andonlyreallyworth.jacksquitintheamount.ofspacethiscantakeupinyourdatabase @home.somewhere.overtherainbow.ornear.somepot.of.irishgold.thinkaboutthis. thisemailisthemaximumlengthallowed.co.uk.com horsesandgoastandcatsanddogsandfleas@some.petting.zoo.org.uk.com )。

示例:

@

现在请记住,这封电子邮件看起来不长,比其他电子邮件更实际,但每个角色(.thisIsAnEmailOfUTF8CharasandA@IntheRightPlace.com 除外)都会需要使用4byte UTF-8编码才能达到MySQL唯一索引限制,例如,如果电子邮件中的每个字符都是某种非拉丁语言,例如埃塞俄比亚语或某些UTF-8中文集。

第3 值得注意的是,中文(以及我认为日文)字符本身就是单词或syllabales (因此比简单字母更大),所以(我有危险)很少有中国人会有过多的电子邮件地址,而不是你有:

.

这是@ *,占用中文的10个字符空格,而ascii拉丁占用20个字符空格。

此外,还有一些(子)中文和日文字符集仍然不存在于UTF-8标准中。 (令人讨厌的是,上面的例子就是其中之一)。

* ^ 谷歌翻译,所以可能是错的!

一些结论选项

  • 将您的电子邮件以纯文本UTF-8存储在具有唯一AI列的特定表格中(如上所述)。引用/交叉引用列AI ID号以发现电子邮件文本在数据库中的任何其他字段/列上是否唯一。不要将电子邮件列唯一,只需将其编入索引,但将索引引用唯一添加到该列。

  • 将电子邮件地址存储为哈希,并检查哈希是否唯一,例如PHP中的猫@空间农场.com donkey@spacefarm.com比MD5更好,因为它是一个更长的哈希,所以可以接受更多的值而不会发生冲突(尽管仍然可能发生碰撞)。 Sha哈希值总是160位或40个字符,因此可以很好地适应MySQL唯一的列约束。

  • 将您的电子邮件地址存储为sha1长度,并希望覆盖98%以上的数据库用户。

  • MySQL唯一索引限制不太可能影响您的电子邮件,而不是有效电子邮件长度的标准。

  • 您可以使用技术上有效的电子邮件地址,但路由器和DNS服务器接受的天气几乎取决于每台服务器。

  • 电子邮件是一种旧的,不合时宜的传输数据的方式。考虑未来将更像SnapChat [示例]和其他基于数据库的经过身份验证的通信,这些通信几乎没有电子邮件继承的限制。电子邮件编码也非常繁琐,并且容易出现各种各样的问题错误和问题,以及极差的安全开销。

MySQL存储电子邮件地址

选项1 )散列电子邮件地址并将散列存储在唯一列中。

  • Postives: 这意味着您可以将电子邮件存储在与您最初预期相同的列中。电子邮件应该是固定长度SHA1哈希。 MySQL Unique列contstraint是有效的。

  • 否定 可能会发生哈希冲突,电子邮件地址本身不可搜索或可编码&#34;。

选项2 )将电子邮件地址明文存储在UTF-8列中,并将电子邮件VARCHAR(190)字段大小限制为190个字符。

  • 肯定:可能涵盖所有可能有效的电子邮件地址。

  • 否定: 较长的电子邮件地址将无效并被截断,这意味着它们将被保存而不会出现错误,但不会是相同的文本字符串(由于截断)。

选项3 )将电子邮件存储在新MySQL表中,其中包含索引sha列和VARCHAR数字参考列,如上所述。

这将意味着电子邮件文本的任何出现都将被数据库中该行的数字引用所取代。具有原始电子邮件文本的列可以是唯一索引。

  • 肯定: 这意味着您可以将电子邮件存储为唯一实体,并且可以执行SQL检查(如果它们已经出现)。

  • 否定: 这意味着稍微改变您当前的编码和SQL命令以适应这个新表作为参考表。

实施例

电子邮件参考表:

VARCHAR

用户(示例)表:

auto_increment

上述CREATE TABLE `email_reference` ( `id` int(8) NOT NULL AUTO_INCREMENT, `email` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`), KEY `email` (`email`(191)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 表格将有一个CREATE TABLE `userdata` ( `user_id` int(8) NOT NULL AUTO_INCREMENT, `name` varchar(90) COLLATE utf8mb4_unicode_ci NOT NULL, `email_ref` int(11) DEFAULT NULL, `details` text COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`user_id`), UNIQUE KEY `email_ref` (`email_ref`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 的唯一列,该列将引用该电子邮件表。此唯一列表示没有两个userdata行可以引用email ref表中的同一行。

因为它是userdata列,所以如果任何人因任何原因没有电子邮件或其他此类&#34;唯一性转义&#34;那么最好允许NULL值。的情况。

我的长篇文章的长期和短期是我认为您的问题似乎主要是边缘情况或由于不完善的数据库结构设计,而不是由于字符集或唯一密钥本身的问题。如果您在系统中设想的不是边缘情况,那么使用我在上面概述的MySQL email_reference参考系统应该有一点远见,满足您的需求。

答案 1 :(得分:2)

我在自己的回答中添加了缺失的细节(特别感谢 Ignacio andig Martin Markus Laire 帮助我将这个难题放在一起)。

这个问题有两个问题:

  1. 编码从UTF-8到ASCII的转换
  2. MySQL索引限制为767字节而不启用MySQL的innodb_large_prefix&lt; 5.7.7(默认情况下现在启用此功能)。
  3. 回答“编码从UTF-8转换为ASCII”

    ASCII是UTF-8的子集,因此不能转换所有字符。 ASCII每个字节仅使用128个字符(前128个),而UTF-8字节可以使用更多。理想的解决方案是使用支持每8位字节的所有256种可能性的编码。像cp1252这样的编码支持大多数字符,但即使这是真的,一些字符也是不可见的,最终可能导致问题。

    对于真正的逐字节转换,唯一可靠的选择是使用二进制。对于我们的用户案例,我们使用MySQL,最好的选择是VARBINARY(254)(二进制字段没有编码)。之后,简单易行:

    INSERT into user_table set email_address='@.';
    SELECT * FROM user_table where email_address = '@.';
    

    为了安全起见,如果需要,值也可以是应用程序端客户端上的HEX('')。对于这个问题,这确实是最有效的解决方案,因为您只会将电子邮件地址存储在254字节的列中,这是RFC标准中任意编码的最大长度。

    回答“MySQL索引限制为767字节”

    看起来InnoDB大前缀现在是MySQL&gt; = 5.7.7的默认配置,因为它主要是backward compatible setting。虽然可以实现这种复杂的UTF-8到HTML-ENTITIES转换,但在使用UTF-8电子邮件地址作为主键时,升级MySQL可能更有意义。或者也可以简单地在MySQL配置中为MySQL&lt; = 5.7.7:

    启用大前缀
    innodb_large_prefix=on
    innodb_file_format=barracuda
    

    <强> Conslusion

    请记住,虽然有些提供商在电子邮件地址中支持UTF-8,但在2016年仍然不是主流。与此同时,有一些选项可以存储信息,但要确保它能够到达目的地。

答案 2 :(得分:0)

你不能&#34;转换&#34;如果字符没有ASCII示例,则UTF8字符串与ASCII的长度相同。

您可以做的是创建构成UTF8字符的字节码的某种表示形式。我怀疑这对于电子邮件地址有用。

<强>更新

在UTF8中,每个字符可以消耗多个字节。多少因人而异。如果ASCII一个字符是一个字节。因此,您可以使用UTF8字符的每个字节,并查看该字节在ASCII中表示的聊天字符。但是,除了那些由单个字节表示的UTF8字符外,这与原始的UTF8字符完全没有关系。恕我直言,那些将匹配他们的ASCII表示。