UUID和大规模应用性能
当数据库的规模增长超过数百万行时,建议开始扩展应用程序,并将数据库分成多个物理服务器。
将数据库分割成多个部分的最大问题是,如果用户要求特定的数据,它的后续同步。
为什么要使用UUID,它比自动增量有什么优势
假设你有一个 "文章 "表,但由于你有一个巨大的网站,上面有数千万篇文章,你必须在多台机器上物理分割它们。
如果我们使用一个普通的整数作为`id'(主键),并设置为自动递增,我们会很快发现,当以分散的方式在不同的机器上创建记录,然后同步它们时,会出现ID碰撞,我们不得不以复杂的方式对记录重新编号。此外,如果我们要将许多会话解析到其他表中,这可能是一个非常复杂的开销,其中很容易出错。
因此,我们可以生成一个`UUID',而不是一个数字标识符,这是一个文本字符串,由一个复杂的算法生成,保证它是唯一的,即使它在多台机器上独立生成。
优势。
- 如果你有多个独立的数据库,然后进行同步,使用UUID意味着一个ID在所有的数据库中都是唯一的,而不仅仅是你所在的数据库和它生成的地方。当合并为一个集群时,不会产生冲突。
- 你可以在实际插入记录到数据库之前知道你的 "主键"。这减少了SQL查询的数量,简化了事务逻辑,而且你可以在记录集合存在之前轻松地将其作为外键使用。
- UUID不会透露日期和序列数的信息,在URL中使用更安全。 例如,如果我发现我是用户
19010018,很容易猜到用户19010017和其他人也存在。这种攻击被称为矢量攻击。
生成一个新的UUID
UUID既可以通过简单的SQL查询SELECT UUID();获得,但这增加了对数据库的查询次数,而且我们失去了在应用逻辑中先批量准备数据,然后一次性写入的能力。
因此,我喜欢使用Composer获得的ramsey/uuid包作为一个好的解决方案。UUID本身有几个版本,软件包可以根据需要俏皮地生成各种版本。
这使得它易于使用。
require 'vendor/autoload.php';use Ramsey\Uuid\Uuid;// 生成版本1(基于时间)的UUID对象$uuid1 = Uuid::uuid1();echo $uuid1->toString() . "\n"; // e4eaaaf2-d142-11e1-b3e4-080027620cdd// 生成第3版(基于名字和MD5散列的)UUID对象$uuid3 = Uuid::uuid3(Uuid::NAMESPACE_DNS, 'php.net');echo $uuid3->toString() . "\n"; // 11a38b9a-b3da-360f-9353-a5a725514269// 生成第四版(随机)UUID对象$uuid4 = Uuid::uuid4();echo $uuid4->toString() . "\n"; // 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a// 生成第5版(基于名字并以SHA1形式散列)UUID对象$uuid5 = Uuid::uuid5(Uuid::NAMESPACE_DNS, 'php.net');echo $uuid5->toString() . "\n"; // c4a760a8-dbcf-5254-a0d9-6a4474bd1b62
如果你使用Doctrine,有一个扩展ramsey/uuid-doctrine,可以直接生成ID,作为一种数据类型。
数据库中的物理存储
在我的第一次尝试中,我使用varchar(36)作为主键(ID),但这根本不是一个好主意。
对内部逻辑的解释:
MySql数据库(和其他许多数据库)不能有效地使用
varchar、char或其他表达字符串的数据类型作为主键。 在一些数据库中,有一个GUID数据类型,被设计用来直接存储UUID。如果你不能使用这种类型,有一个合适的替代品,形式为二进制(16)。
当物理检查数据库时,ID会以HEX格式表示(因为二进制格式无法显示),而不是漂亮的ID726c67c4-e5eb-4a4c-8fcc-031da5d6f3c6,你只会看到726C67C4E5EB4A4C8FCC031DA5D6F3C6,看起来像INSERT查询中的`'?kYߟKg2c;'。
将原始数据从varchar(36)转换为binary(16)。
我假设你在数据库中表示(或计划表示)新设置的ID为。
`id` binary(16) NOT NULL
然而,仅仅改变数据类型是行不通的,所以像这样的情况。
SET FOREIGN_KEY_CHECKS=0;ALTER TABLE article CHANGE id id BINARY(16) NOT NULLSET FOREIGN_KEY_CHECKS=1;
基本上有两个原因。
- 主键和它的会话
必须有相同的数据类型。因此,你需要改变文章ID的数据类型,例如,在将文章与作者匹配的关系表中。 - 二进制格式包含的内容与原始字符串略有不同。你需要使用一个转换函数。
因此,唯一正确的解决方案是备份数据(但无论如何,你应该在每次迁移之前做这件事),准备一个具有功能关系的空数据库,并通过迁移将数据再次放到那里。
如果你以前产生的UUID很奇怪,最好选择一些连续的方法来获得UUID,并对所有记录重新编号。原因是顺序布局允许更好地对值进行排序,并创建一个btree',这使得性能几乎与bigint'相同。
如果你知道有什么更好的方法可以将现有的数据库从UUID存储为varchar格式转换为二进制格式,而不必设计复杂的迁移,并保留外键,我将非常感谢你的反馈。