相较于标准Guid,Sequential Guid在性能上有哪些改进?[关闭]


67

有人曾在用作数据库内的主键时测量过顺序向导与标准向导的性能吗?


我认为不需要唯一键是可猜测的,从Web UI或其他部分传递它们本身似乎是一种不好的做法,并且如果您有安全方面的顾虑,我看不到如何使用guid可以改善事情(如果是这样的话,请使用使用框架的适当加密函数的真实随机数生成器)。
我的方法涵盖了其他项目,可以从代码生成顺序的guid,而无需DB访问(即使仅对于Windows),并且在时间和空间上是唯一的。
是的,提出这个问题的目的是为了回答那些选择了Guid进行PK的人们一种改善数据库使用率的方法(在我的情况下,这使客户能够承受更大的工作量而不必更换服务器)。

看来,安全性问题很多,在这种情况下,请不要使用Sequential Guid,或者最好不要将标准Guid用于PK,这些PK从UI来回传递,而sequential guid用于其他所有操作。与往常一样,没有绝对的真理,我也编辑了主要答案以反映这一点。

Answers:


110

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上都发生问题



可能的解决方案是使用顺序

引导,其生成如下: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)
  • 大量插入操作,在单个事务中一起批处理
  • 从1到100s的刀片,具体取决于工作台
  • 一些表> 1000万行



实验室测试– SQL Server

VS2008测试,有10个并发用户,没有思考时间,基准测试过程有600个批量插入,用于叶表
Standard Guid
Avg。处理时间:平均10.5
。第二要求:平均54.6
。分别 时间:0.26

顺序引导
平均 处理时间:平均4.6
。第二要求:平均87.1
。分别 时间:0.12

在Oracle上的结果(抱歉,用于测试的其他工具)1.327.613插入到具有Guid PK

Standard Guid的表上,时间为0.02秒。每次插入的经过时间为2.861秒。的CPU时间,总计31.049秒 经过

连续的Guid0.00秒。每个插入件的经过时间1.142秒。CPU时间总计3.667秒。经过

的DB文件顺序读取等待时间从640万个等待事件(62.415秒)变为120万个等待事件(11.063秒)。

重要的是要知道所有顺序的guid都可以猜到,因此,如果出于安全考虑,最好还是使用标准guid来使用它们。
简而言之...如果您将Guid用作PK,则每次不从UI来回传递它们时,都使用顺序guid,它们将加快操作速度,并且不花费任何实现成本。


借助存储引擎“ InnoDB”,MySQL以集群方式通过PK存储记录,因此您在这里也应受益于顺序GUID。
hgoebl

1
“很重要的一点是,可以猜出所有顺序的guid,因此,如果出于安全考虑,使用它们并不是一个好主意。”在这种情况下,可以使用Comb guid来代替,因为它具有顺序和随机的优点。
彼得

1
请参阅此博客文章:blogs.msdn.com/b/dbrowne/archive/2012/07/03/… “ ...关于SQL Server的排序顺序,UuidCreateSequential的结果不是顺序的...使它们成为顺序的SQL Server内部NEWSEQUENTIALID函数在GUID上执行某些字节转换...您需要执行相同的字节转换“
George Chakhidze 2014年

我不了解的原因为何更好。
约翰尼

1
使用顺序引导而不是顺序整数的目的是什么?
entonio '20

59

我在这里可能会遗漏一些东西(如果可以的话,请随时进行纠正),但是对于主键使用顺序的GUID / UUID,我看不到什么好处。

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

  • 它们可以在任何地方创建而无需联系数据库
  • 它们是在您的应用程序中完全唯一的标识符(对于UUID,通常是唯一的)
  • 给定一个标识符,就无法在强行强制巨大的键空间之外猜测下一个或上一个(甚至其他任何有效的标识符)。

不幸的是,根据您的建议,您将失去所有这些东西。

所以,是的。您使GUID更好了。但是在此过程中,您一开始就放弃了几乎所有使用它们的理由。

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

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


1
除了“不猜测”(我认为不重要,我们不希望使用随机函数)之外,顺序guid恰好具有您要寻找的特征,我使用C#代码生成它们,它们在时间和时间上都是唯一的空间。
massimogentilini

16
顺序UUID不能保证全局排序。它们在全球范围内仍然是唯一的,但它们在本地也是顺序的。这意味着在不同的主机/进程/线程(取决于顺序方案)上生成的ID会随机交织,但是将对在同一环境中生成的ID进行排序。

2
COMB GUID是有序的,插入/读取速度非常快,并且提供与Identity列相当的速度。标识列的所有属性,但您无需在GUID中使用任何疯狂的复制策略。您要做的身份列。优势GUID。
bbqchickenrobot 2011年

如果它在云上,从长远来看,标准的自动递增整数主键就不好了。
GoYun.Info 2014年

它在表之间不是唯一的。云是针对网络规模的。除非您的数据库很小,否则没关系。
GoYun.Info

23

正如massimogentilini所说,使用UuidCreateSequential(在代码中生成向导时)可以提高性能。但是似乎缺少一个事实:SQL Server(至少是Microsoft SQL 2005/2008)使用相同的功能,但:.NET和SQL Server上Guid的比较/顺序不同,这仍然会导致更多的IO,因为这些向导将无法正确订购。为了生成正确排序的sql服务器的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);
}

此链接此链接


1
好点。据我
所知



4

如果您需要使用顺序的GUId,SQL Server 2005可以通过该NEWSEQUENTIALID()函数为您生成它们。

但是,由于GUIds的基本用法是生成无法猜测的键(或备用键)(例如,避免人们在GET上传递已猜测的键),因此我不知道它们的适用性,因为它们很容易被猜测。

MSDN

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


4
我再说一遍,我看不到Guid用于生成不能猜测的密钥,而是作为一种具有在时间和空间上唯一且可以轻松用于复制的密钥的方法,如果隐私很重要,请使用其他方法(实际随机数)
massimogentilini

3

由Jimmy Nilsson检出COMB:这是一种GUID,其中许多位已替换为类似时间戳的值。这意味着可以对COMB进行排序,并且当用作主键时,插入新值时将减少索引页拆分。

将uniqueidentifier(GUID)用作主键可以吗?


2
我对COMB和类似技术有些怀疑,因为“ GUID在全球范围内是唯一的,但GUID的子字符串不是”:blogs.msdn.com/oldnewthing/archive/2008/06/27/8659071.aspx
Constantin

6
GUID在统计上是唯一的。即,碰撞的机会很小。COMB牺牲了GUID中的128个可用位。因此,是的,发生碰撞的机会较高,但仍然极低。
米奇小麦

guid的全部要点是,它们比整数具有更高的全局唯一性概率。该概率不一定是100%。尽管使用COMB引导增加了发生碰撞的可能性,但它仍比使用标识列低很多数量级。
托马斯


2

好的,我终于自己设计和生产了。

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

当在postgres中插入时,完全随机UUID和COMB _GUID之间的速度比对COMB_GUID有利。在100万条记录的测试中,通过多项测试,COMB_GUID在我的硬件上的速度提高了2倍。记录包含id(22个字符),字符串字段(110个字符),双精度和INT。

在ElasticSearch中,两者之间没有明显的区别以进行索引。作为内容被送入时间有关,或可在id字段被预先排序,以便它我仍然会在内容的情况下使用COMB_GUIDS去BTREE索引链中的任何地方IS时间有关,部分连续的,它会加快。

非常有趣。下面是制作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() );
    }

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.