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

Nic*_*tte 1 php mysql string encoding utf-8

我正在尝试将 UTF-8 字符支持到电子邮件地址中。如果我理解正确,电子邮件地址限制为254 个可用 (ASCII) 字符。基于此,我想将电子邮件地址存储在 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/>';
Run Code Online (Sandbox Code Playgroud)

输出是:

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 中启用大索引,我宁愿避免。

有没有办法在不使用innodb_large_prefix=onMySQL 的情况下实现我想要做的事情?

Mar*_*tin 5

Nicholas,您似乎对问题中的 Ascii Vs UTF-8 字符集和您要回答的评论有一些基本的混淆。

UTF-8 值:@。

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

ASCII 长度:5

ASCII 值:?@?。

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

不,如果 Panda Face UTF-8 字符用 Ascii 表示,它将如何表示?充其量这将是主观的,例如使用 a<3或 aB-)等。

Pandaface 没有翻译,因此它被替换?为输出字符集中的占位符。这有点像试图拼写 king 但只能用元音。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. 
Run Code Online (Sandbox Code Playgroud)

因此 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
Run Code Online (Sandbox Code Playgroud)

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

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

一些进一步的想法

1 htmlentities不是一个有效的解决方案,因为对于任何字符,它的实体的大小总是更大,取>字符,在最好的情况下&gt;长度已经是 4 个字符,即使每个字符都可以存储在“1 个字节”中,这仍然是一个比一般 UTF-8 字符在最坏情况下为 4 字节更大的存储空间。>

htmlentities将只显示有一个指定的HTML选择,而且我不确定,如果事情像效果字符<PandaFace><shitpoo>有ヶ辆(?)。

2您见过甚至用过的最长的电子邮件地址是多少,这是真正的真实地址?电子邮件地址的最大大小为 254 个 ascii 字符,即:

thisisaverylongandtediousemailaddresswhichisprettyimpractical.
andonlyreallyworth.jacksquitintheamount.ofspacethiscantakeupinyourdatabase
@home.somewhere.overtherainbow.ornear.somepot.of.irishgold.thinkaboutthis.
thisemailisthemaximumlengthallowed.co.uk.com
Run Code Online (Sandbox Code Playgroud)

现在看看那个代码,根据定义,这是允许的最长的 ascii 电子邮件地址。这很长,虽然并非不可能,但拥有这种长度的电子邮件地址(ASCII 格式)的用户数量将是一个极端的边缘情况。

更进一步,假设您的电子邮件地址是 64 个 UTF-8 4 字节字符,因为您已将其设置为 utf-8 上限,

所以作为 ascii 长度的东西:

  horsesandgoastandcatsanddogsandfleas@some.petting.zoo.org.uk.com
Run Code Online (Sandbox Code Playgroud)

但是,由于 UTF-8 4byte 字符和上面的电子邮件被翻译成某些 UTF-8 中文字符集,因此该电子邮件地址长度仍然是人类实际使用并作为其地址的实用范围的上限。但它并没有完全脱离公园,除非您针对特定的市场受众,否则不太可能。

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

例子:

thisIsAnEmailOfUTF8CharasandA@IntheRightPlace.com
Run Code Online (Sandbox Code Playgroud)

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

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

?@????.com 
Run Code Online (Sandbox Code Playgroud)

这是donkey@spacefarm.com*,中文占10个字符空格,而ascii拉丁文占20个字符空格。

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

*^谷歌翻译,所以可能有误!

一些结论选项

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

  • 将电子邮件地址存储为散列并检查散列是否唯一,例如sha1在 PHP 中。SHA1比 MD5 更好,因为它是更长的散列,因此可以接受更多值而不会发生冲突(尽管仍然可能发生冲突)。Sha 哈希总是 160 位或 40 个字符长,因此很适合 MySQL 唯一列约束。

  • 将您的电子邮件地址存储到一定VARCHAR(190)长度,并期望覆盖 98% 以上的数据库用户。

  • MySQL 唯一索引限制不像有效电子邮件长度的标准那样影响您的电子邮件。

  • 您可能可以避免使用技术上有问题的电子邮件地址,但这些电子邮件地址是否被路由器和 DNS 服务器接受几乎取决于每个服务器。

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


MySQL 存储电子邮件地址

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

  • 正面: 这意味着您可以将电子邮件存储在您最初打算的同一列中。电子邮件应该是固定长度的sha哈希。MySQL 唯一列约束将是有效的。

  • 散列冲突是可能的,电子邮件地址本身将不可搜索或“可解码”。

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

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

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

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

这意味着电子邮件文本的任何出现都将被数据库中该行的数字引用替换。以原始电子邮件文本为特征的列可以是唯一索引。

  • 正面: 这意味着您可以将电子邮件存储为唯一实体,并且可以执行 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
Run Code Online (Sandbox Code Playgroud)

用户(示例)表:

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
Run Code Online (Sandbox Code Playgroud)

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

因为它是一UNIQUE列,所以如果有人出于任何原因没有电子邮件或其他此类“唯一性转义”情况,则允许使用 NULL 值是个好主意。


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