69

当用作数据库内的主键时,是否有人测量过 Sequential Guid 与 Standard Guid 的性能?


我不认为需要唯一的密钥是可猜测的,从 Web UI 或其他部分传递它们本身似乎是一种不好的做法,如果您有安全问题,我看不到如何使用 guid 可以改进事情(如果这是问题,请使用使用框架的适当加密功能的真实随机数生成器)。
我的方法涵盖了其他项目,可以从代码生成顺序 guid,而无需访问数据库(也适用于 Windows),并且它在时间和空间上是独一无二的。
是的,提出问题的目的是为了回答它,为那些选择 Guid 进行 PK 的人们提供一种提高数据库使用率的方法(在我的例子中,它允许客户在无需更改服务器的情况下维持更高的工作量)。

似乎安全问题很多,在这种情况下不要使用顺序 Guid,或者更好的是,对 PK 使用标准 Guid,这些 Guid 从您的 UI 来回传递,而顺序 guid 用于其他所有内容。与往常一样,没有绝对的真理,我也编辑了主要答案以反映这一点。

4

8 回答 8

114

GUID 与顺序 GUID



一种典型的模式是使用 Guid 作为表的 PK,但是,正如其他讨论中提到的(请参阅GUID / UUID 数据库键的优点和缺点),存在一些性能问题。

这是一个典型的 Guid 序列

f3818d69-2552-40b7-a403-01a6db4552f7
7ce31615-fafb-42c4-b317-40d21a6a3c60
94732fc7-768e-4cf2-9107-f0953f6795a5


这类数据的问题是:<
-

  • 广泛的价值分布
  • 几乎随机的
  • 索引使用非常非常非常糟糕
  • 很多叶子在移动
  • 几乎每个 PK 都需要至少在一个非聚集索引上
  • Oracle 和 SQL Server 都出现问题



一种可能的解决方案是使用按如下方式生成的顺序 Guid:

cc6466f7-1066-11dd-acb6-005056c00008
cc6466f8-1066-11dd-acb6-005056c00008
cc6466f9-1066-11dd-acb6-005056c00008


如何从 C# 代码生成它们:

[DllImport("rpcrt4.dll", SetLastError = true)]
static extern int UuidCreateSequential(out Guid guid);

public static Guid SequentialGuid()
{
    const int RPC_S_OK = 0;
    Guid g;
    if (UuidCreateSequential(out g) != RPC_S_OK)
        return Guid.NewGuid();
    else
        return g;
}


好处

  • 更好地使用索引
  • 允许使用集群键(在 NLB 场景中验证)
  • 更少的磁盘使用量
  • 以最低成本提高 20-25% 的性能



现实生活测量: 场景:

  • 在 SQL Server 上存储为 UniqueIdentifier 类型的 Guid
  • 在 Oracle 上存储为 CHAR(36) 的 Guid
  • 大量插入操作,在单个事务中批量处理
  • 从 1 到 100 次插入,具体取决于表
  • 一些表 > 1000 万行



实验室测试 - SQL Server

VS2008 测试,10 个并发用户,无思考时间,基准过程,600 次批量插入叶表
标准 Guid
Avg。处理持续时间:10.5
平均。第二次请求:54.6
平均。响应。时间:0.26

Sequential Guid
Avg。过程持续时间:4.6
平均。第二次请求:87.1
平均。响应。时间:Oracle 上的0.12 结果(对不起,用于测试的不同工具)1.327.613 在具有 Guid PK标准 Guid的表上插入,0.02秒。每个插入的经过时间,2.861秒。CPU 时间,总计



31.049秒。经过 的

顺序指导0.00秒。每个插入的经过时间,1.142秒。CPU 时间,总共3.667秒。elapsed

DB 文件顺序读取等待时间从62.415 秒的640万个等待事件变为11.063秒的120万个等待事件。 重要的是要看到所有顺序 guid 都可以猜到,所以如果安全是一个问题,使用它们不是一个好主意,仍然使用标准 guid。


简而言之...如果您将 Guid 用作 PK,则每次不从 UI 来回传递它们时都使用顺序 guid,它们将加快操作并且不需要任何成本来实现。

于 2008-10-04T13:52:51.390 回答
62

我可能在这里遗漏了一些东西(如果我有,请随时纠正我),但我看不到将顺序 GUID/UUID 用于主键的好处很小。

在自动递增整数上使用 GUID 或 UUID的要点是:

  • 它们可以在任何地方创建而无需联系数据库
  • 它们是在您的应用程序中完全唯一的标识符(在 UUID 的情况下,是普遍唯一的)
  • 给定一个标识符,除了暴力破解一个巨大的键空间之外,没有办法猜测下一个或前一个(甚至任何其他有效的标识符) 。

不幸的是,使用你的建议,你失去了所有这些东西。

所以,是的。您使 GUID 变得更好。但是在这个过程中,你已经抛弃了几乎所有首先使用它们的理由。

如果您真的想提高性能,请使用标准的自动递增整数主键。这提供了您描述的所有好处(以及更多),同时在几乎所有方面都比“顺序指南”更好。

这很可能会被遗忘,因为它没有专门回答你的问题(这显然是精心设计的,所以你可以立即自己回答),但我觉得这是一个更重要的问题。

于 2008-10-04T15:36:59.230 回答
23

正如 massimogentilini 已经说过的,使用 UuidCreateSequential(在代码中生成 guid 时)可以提高性能。但是似乎缺少一个事实:SQL Server(至少 Microsoft SQL 2005 / 2008)使用相同的功能,但是:Guid 的比较/排序在 .NET 和 SQL Server 上不同,这仍然会导致更多 IO,因为不会正确订购指南。为了生成正确排序的 sql server(排序)的 guid,您必须执行以下操作(请参阅比较详细信息):

[System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
static extern int UuidCreateSequential(byte[] buffer);

static Guid NewSequentialGuid() {

    byte[] raw = new byte[16];
    if (UuidCreateSequential(raw) != 0)
        throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());

    byte[] fix = new byte[16];

    // reverse 0..3
    fix[0x0] = raw[0x3];
    fix[0x1] = raw[0x2];
    fix[0x2] = raw[0x1];
    fix[0x3] = raw[0x0];

    // reverse 4 & 5
    fix[0x4] = raw[0x5];
    fix[0x5] = raw[0x4];

    // reverse 6 & 7
    fix[0x6] = raw[0x7];
    fix[0x7] = raw[0x6];

    // all other are unchanged
    fix[0x8] = raw[0x8];
    fix[0x9] = raw[0x9];
    fix[0xA] = raw[0xA];
    fix[0xB] = raw[0xB];
    fix[0xC] = raw[0xC];
    fix[0xD] = raw[0xD];
    fix[0xE] = raw[0xE];
    fix[0xF] = raw[0xF];

    return new Guid(fix);
}

此链接此链接

于 2009-06-25T08:06:54.103 回答
5

见这篇文章:(http://www.shirmanov.com/2010/05/generating-newsequentialid-compatible.html

即使 MSSql 使用相同的函数来生成 NewSequencialIds ( UuidCreateSequential(out Guid guid) ),MSSQL 也会反转第 3 和第 4 字节模式,这与在代码中使用此函数时得到的结果不同。Shirmanov 展示了如何获得与 MSSQL 完全相同的结果。

于 2011-02-18T05:01:44.940 回答
4

查看Jimmy Nilsson 的COMB:一种 GUID,其中许多位已替换为类似时间戳的值。这意味着 COMB 可以排序,并且当用作主键时,在插入新值时会减少索引页面拆分。

可以使用唯一标识符 (GUID) 作为主键吗?

于 2008-10-04T15:46:26.107 回答
4

如果您需要NEWSEQUENTIALID()使用顺序 GUId,SQL Server 2005 可以使用该功能为您生成它们。

然而,由于 GUId 的基本用法是生成无法猜测的密钥(或备用密钥)(例如,为了避免人们在 GET 上传递猜测的密钥),我看不出它们有多适用,因为它们很容易被猜到。

来自MSDN

重要提示:
如果担心隐私问题,请勿使用此功能。可以猜测下一个生成的 GUID 的值,因此可以访问与该 GUID 关联的数据。

于 2008-10-04T15:59:44.943 回答
4

好的,我自己在设计和制作上终于走到了这一步。

我生成了一个 COMB_GUID,其中高 32 位基于 Unix 时间的第 33 位到第 1 位,以毫秒为单位。因此,每 2 毫秒有 93 位随机性,高位的翻转每 106 年发生一次。COMB_GUID(或类型 4 UUID)的实际物理表示是 128 位的 base64 编码版本,即 22 个字符的字符串。

在 postgres 中插入时,完全随机的 UUID 和 COMB _GUID 之间的速度比对 COMB_GUID 有利。COMB_GUID在我的硬件上经过多次测试快2倍,用于一百万条记录测试。记录包含 id(22 个字符)、一个字符串字段(110 个字符)、一个双精度和一个 INT。

在 ElasticSearch 中,两者在索引方面没有明显区别。我仍将使用 COMB_GUIDS 以防内容进入链中任何位置的 BTREE 索引,因为内容与时间相关,或者可以在 id 字段上进行预排序,使其时间相关且部分顺序,它将加快速度。

非常有趣。制作 COMB_GUID 的 Java 代码如下。

import java.util.Arrays;
import java.util.UUID;
import java.util.Base64; //Only avail in Java 8+
import java.util.Date;

import java.nio.ByteBuffer; 

    private ByteBuffer babuffer = ByteBuffer.allocate( (Long.SIZE/8)*2 );
private Base64.Encoder encoder = Base64.getUrlEncoder();
public  String createId() {
    UUID uuid = java.util.UUID.randomUUID();
        return uuid2base64( uuid );
}

    public String uuid2base64(UUID uuid){ 

        Date date= new Date();
        int intFor32bits;
        synchronized(this){
        babuffer.putLong(0,uuid.getLeastSignificantBits() );
        babuffer.putLong(8,uuid.getMostSignificantBits() );

                long time=date.getTime();
        time=time >> 1; // makes it every 2 milliseconds
                intFor32bits = (int) time; // rolls over every 106 yers + 1 month from epoch
                babuffer.putInt( 0, intFor32bits);

    }
        //does this cause a memory leak?
        return encoder.encodeToString( babuffer.array() );
    }

}

于 2013-09-26T21:54:29.620 回答
4

我使用实体框架混淆了 Guid(集群和非集群)、顺序 Guid 和 int(身份/自动增量)之间的区别。与具有身份的 int 相比,Sequential Guid 的速度惊人地快。Sequential Guid 的结果和代码在这里

于 2013-11-01T20:42:52.320 回答