php - 如何使用PHP将UTF-8电子邮件地址存储在唯一的MySQL列中?

标签 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来实现我要执行的操作?

最佳答案

Nicholas,您似乎对“问题”中的“ Ascii Vs UTF-8字符集”和对答案的评论有一些根本性的混淆。


  
    UTF-8值:🐼@🐼.🐼
    
    ASCII长度(来自UTF-8字符串):14
    
    ASCII长度:5
    
    ASCII值:?@?。
  
  
  我希望转换后的ASCII字符串长度为14个字符吗?


不,如果用Ascii表示Panda Face UTF-8字符,将如何表示?充其量是主观的,例如<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),如果不存在,则会插入该电子邮件地址并由id列引用。

email列始终为UTF8mb4,因为它是完整的UTF8,与MySQL标准utf8_归类不同。 MySQL不能像您所说的那样唯一地限定大于767字节的数据,但是如果您的其他各个表引用了电子邮件的id行,则其他表上的该列可以是唯一的。

一些进一步的想法

1 htmlentities不是有效的解决方案,因为对于任何字符,其实体的大小始终较大,请采用>字符,即&gt;,在最佳情况下,其长度已为4个字符,即使每个字符可以存储在“ 1字节”中,这仍然比>更大,而htmlentities作为最坏情况下的一般UTF-8字符为4个字节。

<PandaFace>仅会影响具有指定HTML备用字符的字符,并且我不确定<shitpoo>@之类的东西是否具有htmlentities(?)。

2您见过或什至使用过的最长的电子邮件地址是真实的真实地址吗?电子邮件地址的最大大小为254个ascii字符,即:

thisisaverylongandtediousemailaddresswhichisprettyimpractical.
andonlyreallyworth.jacksquitintheamount.ofspacethiscantakeupinyourdatabase
@home.somewhere.overtherainbow.ornear.somepot.of.irishgold.thinkaboutthis.
thisemailisthemaximumlengthallowed.co.uk.com


现在查看该代码,根据定义,这是允许的最长ascii电子邮件地址。这相当长,虽然并非不可能,但拥有此长度电子邮件地址(以ascii表示)的用户数量将是一个极端的情况。

将此步骤进一步推到这一行,假设您设置了utf-8上限,则您的电子邮件地址为64个UTF-8 4字节字符,

因此,ascii的长度如下:

  horsesandgoastandcatsanddogsandfleas@some.petting.zoo.org.uk.com


但是,由于上述4字节UTF-8字符已被翻译成某些UTF-8汉字字符集,因此该电子邮件地址长度仍是人类实际使用的地址的上限。但这并不是完全不可能的,除非您针对特定的市场受众,否则这不太可能。

MySQL 767字节的唯一索引将限制您使用大约191个4字节UTF-8字符,然后在包含2个(最多3个)非UTF-8的电子邮件地址中,您将被限制为47个完全UTF-8字符4字节字符(例如..)。

例:

thisIsAnEmailOfUTF8CharasandA@IntheRightPlace.com


现在请记住,这封电子邮件看起来并不长,它的大小比其他电子邮件更真实,但是每个字符(@donkey@spacefarm.com除外)都必须采用4字节UTF-8编码,以便达到MySQL唯一索引限制,例如,如果电子邮件中的每个字符都是某种非拉丁语言,例如埃塞俄比亚语或某些UTF-8中文集。

3
还值得注意的是,中文(我认为日语)字符本身就是每个单词或音节(因此,比单纯的字母大),因此(我冒昧)很少有中文会有过多的电子邮件地址,而您却拥有:

猫@空间农场.com 


这是sha1 *,中文占10个字符空间,而拉丁字母ascii占20个字符空间。

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

* ^ Google翻译,所以可能是错误的!

一些结论选项


将电子邮件以纯文本UTF-8格式存储在具有唯一AI列的特定表中(如上所述)。引用/交叉引用列AI ID号,以发现电子邮件文本在数据库中的任何其他字段/列中是否唯一。不要为电子邮件列设置唯一性,而只需为其建立索引,但要使该列的索引引用唯一。
将电子邮件地址存储为哈希,并检查哈希是否唯一(例如,PHP中的SHA1)。 VARCHAR(190)比MD5更好,因为它是更长的哈希,因此可以接受更多的值而不会发生冲突(尽管仍然可能发生冲突)。 Sha散列始终为160位或40个字符长,因此可以轻松地适合MySQL唯一的列约束。
将您的电子邮件地址存储为sha长度,并希望它能覆盖您数据库用户的98%以上。
MySQL唯一索引限制不会像有效电子邮件长度标准那样影响您的电子邮件。
您可能可以使用在技术上有疑问的有效电子邮件地址,但是如果路由器接受了这些地址,则DNS服务器几乎取决于每台服务器。
电子邮件是一种过时且过时的数据传输方式。考虑到未来将更像SnapChat [示例]和其他基于数据库的经过身份验证的通信,这些通信几乎没有电子邮件继承的限制。电子邮件在编码时非常繁琐,并且容易出现各种问题,错误和问题以及极差的安全性开销。




MySQL存储电子邮件地址

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


正词:
这意味着您可以将电子邮件存储在原定的同一列中。电子邮件应为固定长度的VARCHAR哈希。 MySQL Unique列约束将有效。
负面的
可能发生哈希冲突,电子邮件地址本身不可搜索或“解码”。


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


正面:
这可能会覆盖所有可能的有效电子邮件地址。
负面因素:
较长的电子邮件地址将无效并被截断,这意味着它们将被保存而不会出错,但不会是相同的文本字符串(由于截断)。


选项3)将电子邮件存储在新的MySQL表中,该表具有已索引的auto_increment列和userdata数字参考列,如上所述。

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


正面:
这意味着您可以将电子邮件存储为唯一的实体,并可以对它们是否已经出现进行SQL检查。
负面因素:
这意味着要稍微更改当前的编码和SQL命令,以将该新表作为参考表。




电子邮件参考表:

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


上面的email ref表将具有userdata的唯一列,该列将引用电子邮件表。此唯一列意味着没有两个email_reference行可以引用UNIQUE表中的同一行。

因为它是AI int列,所以最好是允许NULL值,以便任何人由于任何原因没有电子邮件或其他类似的“唯一性转义”情况。



我写的长篇小说的长短之处是,我认为您的担忧似乎主要是边缘情况或由于数据库结构设计不完善,而不是由于字符集或唯一键本身的问题。如果您对系统的设想不是偶然的情况,那么使用我上面概述的MySQL 参考系统,应该具有一点远见,可以满足您的需求。

关于php - 如何使用PHP将UTF-8电子邮件地址存储在唯一的MySQL列中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38806843/

相关文章:

php - 如何使 PHP/MySQL 查询的搜索结果链接到单行中的数据?

php - 我应该如何创建我的数据映射器/域对象/服务?

MySQL:计算行数的问题

C 语言 - strcmp 始终返回 0 而 strcpy 不会将字符串复制到另一个数组中

php - 将 3 个平面索引数组合并为一个平面索引数组

php - 使用案例将数据插入临时 mysql 表

mysql 选择行然后复制行并更新值

java - 使用 MySQL 从 Java 中的多个表中选择带前缀的列

php - 使用 implode 时数组到字符串的转换错误

python 2.7 : matching expression using regex