PHP Manual
/
性能优化

UUID和大规模应用性能

08. 11. 2019

Obsah článku

当数据库的规模增长超过数百万行时,建议开始扩展应用程序,并将数据库分成多个物理服务器。

将数据库分割成多个部分的最大问题是,如果用户要求特定的数据,它的后续同步。

为什么要使用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数据库(和其他许多数据库)不能有效地使用varcharchar或其他表达字符串的数据类型作为主键。 在一些数据库中,有一个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 NULL
SET FOREIGN_KEY_CHECKS=1;

基本上有两个原因。

  • 主键和它的会话必须有相同的数据类型。因此,你需要改变文章ID的数据类型,例如,在将文章与作者匹配的关系表中。
  • 二进制格式包含的内容与原始字符串略有不同。你需要使用一个转换函数。

因此,唯一正确的解决方案是备份数据(但无论如何,你应该在每次迁移之前做这件事),准备一个具有功能关系的空数据库,并通过迁移将数据再次放到那里。

如果你以前产生的UUID很奇怪,最好选择一些连续的方法来获得UUID,并对所有记录重新编号。原因是顺序布局允许更好地对值进行排序,并创建一个btree',这使得性能几乎与bigint'相同。

如果你知道有什么更好的方法可以将现有的数据库从UUID存储为varchar格式转换为二进制格式,而不必设计复杂的迁移,并保留外键,我将非常感谢你的反馈

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
2.
Status:
All systems normal.
2024