Sequential Guid与标准Guid相比性能提高了多less?
在数据库中用作主键时,有人曾经测量过Sequential Guid vs. Standard Guid的性能吗?
GUID与顺序GUID
一个典型的模式是使用Guid作为PK的表格,但是,如其他讨论(请参阅GUID / UUID数据库键的优缺点)中提到的那样,存在一些性能问题。
这是一个典型的Guid序列
f3818d69-2552-40b7-a403-01a6db4552f7
7ce31615-fafb-42c4-b317-40d21a6a3c60
94732fc7-768e-4cf2-9107-f0953f6795a5
这类数据的问题是:<
–
- 价值的广泛分布
- 几乎是随机的
- 索引使用是非常非常非常糟糕的
- 很多叶子在移动
- 几乎每个PK都需要至less在一个非聚集索引上
- 在Oracle和SQL Server上都会出现问题
一个可能的解决scheme是使用Sequential 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场景中validation)
- 更less的磁盘使用
- 性能提高20-25%,成本最低
实际测量:场景:
- Guid在SQL Server上以UniqueIdentifiertypes存储
- Guid在Oracle上以CHAR(36)存储
- 大量的插入操作,在单个事务中一起进行批处理
- 从1到100s的插入取决于表格
- 一些表> 1000万行
实验室testing – SQL Server
VS2008testing,10个并发用户,不用考虑时间,600个批量插入叶表的基准过程
标准指导
平均。 处理时间: 10.5秒
平均。 请求第二个: 54.6
平均。 RESP。 时间: 0.26
顺序Guid
平均。 处理持续时间: 4.6秒
平均。 请求第二个: 87.1
平均。 RESP。 时间: 0.12
对Oracle的结果 (对不起,用于testing的不同工具)1.327.613用Guid PK插入表格
标准指导 , 0.02秒。 每个插入物的经过时间, 2.861秒。 的CPU时间,共31.049秒。 过去
顺序指导 , 0.00秒。 每个插入物的stream逝时间, 1.142秒。 的CPU时间,共计3.667秒。 过去
数据库文件顺序读取等待时间从640万个等待事件( 62.415秒)传递到120万个等待事件( 11.063秒)。
重要的是要看到,所有的顺序guid都可以被猜到,所以如果安全是一个问题,使用它们并不是一个好主意,仍然使用标准的guid。
为了简短起见,如果你使用Guid作为PK使用顺序GUID,每次它们不从UI传回时,它们将加速操作,不需要花费任何东西来执行。
我可能在这里丢失了一些东西(如果我是这样,请随时纠正我),但是在使用主键的顺序GUID / UUID方面我看不出什么好处。
使用GUID或UUID超过自动增量整数的要点是:
- 他们可以在任何地方创build而不需要联系数据库
- 它们是您的应用程序中完全唯一的标识符(在UUID的情况下,通用唯一)
- 给定一个标识符,除了暴力破解之外,没有办法猜测下一个或前一个(甚至是任何其他有效的标识符) – 强制一个巨大的密钥空间。
不幸的是,使用你的build议,你会失去所有这些东西。
所以,是的。 你已经使GUID更好。 但在这个过程中,你几乎把所有使用它们的原因都扔掉了。
如果您真的想提高性能,请使用标准的自动增量整数主键。 这提供了你所描述的所有好处(甚至更多),而且几乎在每个方面都比“顺序指导”更好。
这很可能会被遗忘,因为它没有明确地回答你的问题(这个问题显然是精心制作的,所以你可以立即回答),但是我觉得这是一个非常重要的问题。
正如massimogentilini所说,使用UuidCreateSequential(在代码中生成GUID时)可以提高性能。 但事实似乎是缺less的:SQL Server(至lessMicrosoft SQL 2005/2008)使用相同的function,但是:Guid的比较/sorting在.NET和SQL Server上有所不同,这仍然会导致更多的IO,因为guids将不会被正确地命令。 为了生成正确的命令为sql server(sorting),您必须执行以下操作(请参阅比较详细信息):
[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); }
或此链接或此链接 。
如果您需要使用顺序GUI,SQL Server 2005可以使用NEWSEQUENTIALID()
函数为您生成它们。
然而,由于GUI的基本用法是生成不能被猜测的密钥(或备用密钥)(例如为了避免人们通过在GET上传递猜测密钥),我不明白它们是多么适用,因为它们很容易被猜到。
来自MSDN :
重要:
如果隐私问题,请不要使用此function。 可以猜测下一个生成的GUID的值,因此可以访问与该GUID相关的数据。
看到这篇文章:( http://www.shirmanov.com/2010/05/generating-newsequentialid-compatible.html )
即使MSSql使用这个相同的函数来生成NewSequencialIds(UuidCreateSequential(出Guid guid)),MSSQL反转第三和第四个字节模式,这不会给你在代码中使用这个函数时得到的结果。 Shirmanov展示了如何获得与MSSQL创build完全相同的结果。
查看Jimmy Nilsson的COMBs :一种GUIDtypes,其中一些位已被replace为类似时间戳的值。 这意味着可以对COMB进行sorting,并且当用作主键时,在插入新值时导致索引页面拆分较less。
使用唯一标识符(GUID)作为主键是否可以?
我使用entity framework来区分Guid(聚簇和非聚簇),Sequential Guid和int(Identity / autoincrement)之间的区别。 与具有身份的int相比,Sequential Guid的速度惊人地快。 这里的Sequential Guid的结果和代码 。
我不认为需要唯一的钥匙是可以猜测的,从networking用户界面或其他部分传递他们似乎是一个坏习惯本身,我不明白,如果你有安全问题,如何使用GUID可以提高事情(如果这是事实使用一个真正的随机数发生器使用框架的适当的encryptionfunction)。
其他项目是由我的方法覆盖,一个连续的guid可以从代码生成,而不需要数据库访问(也适用于Windows),它是唯一的时间和空间。
是的,有人提出了回答这个问题的意图,让那些selectGuids为他们的PK提供数据库使用的方法(在我的情况下允许客户在不必更换服务器的情况下承受更高的工作量)。
看起来,安全问题是很多的,在这种情况下,不要使用Sequential Guid,或者更好的是,使用标准的Guid来从你的用户界面(UI)和顺序GUID中返回并转发所有的东西。 一如既往,没有绝对的真理,我也编辑了主要的答案来反映这一点。
好的,我自己终于在devise和制作方面达到了这个地步。
我生成一个COMB_GUID,其中高32位是基于Unix时间的33到1位,以毫秒为单位。 所以,每2毫秒有93位的随机性,高位的翻转每106年发生一次。 COMB_GUID(或types4 UUID)的实际物理表示是128位的base64编码版本,它是一个22个字符的string。
在postgres中插入时,完全随机的UUID和COMB _GUID之间的速度比对COMB_GUID有利。 COMB_GUID在我的硬件上通过多次testing的速度提高了2倍,进行了一百万次loggingtesting。 logging包含id(22个字符),一个string字段(110个字符),一个双精度和一个INT。
在ElasticSearch中,两者之间没有明显的区别。 如果内容与时间相关,或者可以在id字段中预先sorting以便与时间相关并且部分顺序,那么我仍然会使用COMB_GUIDS来防止内容在链中任何地方进入BTREE索引。
非常有趣。 下面是创buildCOMB_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() ); }
}