问题描述


有序的GUID性能对比,堪比自增ID integer

一个大神告诉我NEWSEQUENTIALID() 在数据迁移的时候会有问题(感谢大神指点),所以我就深挖一下这个函数。

    关于NEWSEQUENTIALID() 的用法 参照  NEWSEQUENTIALID()

NEWSEQUENTIALID 是对 Windows UuidCreateSequential 函数的包装。

https://msdn.microsoft.com/zh-cn/library/ms189786(v=sql.120).aspx

我们系统中对UuidCreateSequential 方法的扩展是这样写的,代码如下:

    public static class GuidExtension
{
[DllImport("rpcrt4.dll", SetLastError = true)]
public static extern int UuidCreateSequential(out Guid guid);
private const int RPC_S_OK = ; public static Guid CreateRpcrt4Guid()
{
Guid guid;
int result = UuidCreateSequential(out guid);
if (result == RPC_S_OK)
{
byte[] guidBytes = guid.ToByteArray();
Array.Reverse(guidBytes, , );
Array.Reverse(guidBytes, , );
Array.Reverse(guidBytes, , ); return new Guid(guidBytes);
}
else
return Guid.NewGuid();
} }

  有以下几个缺点:

  1、暴漏MAC地址:NEWSEQUENTIALID函数最后6个字符是网卡的MAC地址

  可以执行看一下

create table #t
(
id uniqueidentifier not null default newsequentialid()
,name varchar(100)
)
go insert into #t(name)
output inserted.id
values('a')

  2、如果进行数据迁移,到另一台机器上,MAC地址改变就会引起页的争用。

    因为GUID在的SQL Server的值大小的比对是这样的:

with uids as (
select id = 1, uuid = cast ('00000000-0000-0000-0000-010000000000' as uniqueidentifier)
union select id = 2, uuid = cast ('00000000-0000-0000-0000-000100000000' as uniqueidentifier)
union select id = 3, uuid = cast ('00000000-0000-0000-0000-000001000000' as uniqueidentifier)
union select id = 4, uuid = cast ('00000000-0000-0000-0000-000000010000' as uniqueidentifier)
union select id = 5, uuid = cast ('00000000-0000-0000-0000-000000000100' as uniqueidentifier)
union select id = 6, uuid = cast ('00000000-0000-0000-0000-000000000001' as uniqueidentifier)
union select id = 7, uuid = cast ('00000000-0000-0000-0100-000000000000' as uniqueidentifier)
union select id = 8, uuid = cast ('00000000-0000-0000-0010-000000000000' as uniqueidentifier)
union select id = 9, uuid = cast ('00000000-0000-0001-0000-000000000000' as uniqueidentifier)
union select id = 10, uuid = cast ('00000000-0000-0100-0000-000000000000' as uniqueidentifier)
union select id = 11, uuid = cast ('00000000-0001-0000-0000-000000000000' as uniqueidentifier)
union select id = 12, uuid = cast ('00000000-0100-0000-0000-000000000000' as uniqueidentifier)
union select id = 13, uuid = cast ('00000001-0000-0000-0000-000000000000' as uniqueidentifier)
union select id = 14, uuid = cast ('00000100-0000-0000-0000-000000000000' as uniqueidentifier)
union select id = 15, uuid = cast ('00010000-0000-0000-0000-000000000000' as uniqueidentifier)
union select id = 16, uuid = cast ('01000000-0000-0000-0000-000000000000' as uniqueidentifier)
)
select * from uids order by uuid desc

输出结果:

  类似 汉字的三点水偏旁(为了好记)


从这里可以看出,MAC地址对GUID的大小有这最高的决定性,这就导致在数据迁移的时候出问题。

COMB解决方案


COMB 类型的GUID 基本设计思路是这样的:既然GUID数据生成是随机的造成索引效率低下,影响了系统的性能,那么能不能通过组合的方式,保留GUID的前10个字节,用后6个字节表示GUID生成的时间(DateTime),这样我们将时间信息与GUID组合起来,在保留GUID的唯一性的同时增加了有序性,以此来提高索引效率。

前十个字节是通过随机数生成

private static readonly RNGCryptoServiceProvider RandomGenerator = new RNGCryptoServiceProvider();

      byte[] randomBytes = new byte[];
RandomGenerator.GetBytes(randomBytes);

后六个字节用时间生成

      long timestamp = DateTime.UtcNow.Ticks / 10000L;
byte[] timestampBytes = BitConverter.GetBytes(timestamp); if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}

最后组合起来

    byte[] guidBytes = new byte[];
Buffer.BlockCopy(randomBytes, , guidBytes, , );
Buffer.BlockCopy(timestampBytes, , guidBytes, , ); return new Guid(guidBytes);

这个解决方法是被大家所认可的,唯一感觉不好的地方是,在快速获取很多的GUID的时候,时间是一样的,加上随机生成的数据,这一组数据是大小不一的。假如数据库里有很多数据,这一组数据肯定比他们都大,性能应该没有问题。

github地址:

https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs#L25-L72

https://github.com/jhtodd/SequentialGuid/

我的解决方法


总结上面的方法,UuidCreateSequential 前面10个字节有序,后6个是MAC地址。COMBO解决方案是前面10个随机,后六个是时间。我是将这两个结合起来

前10个去UuidCreateSequential 方法的值,后6个取时间

代码:

public static Guid NewSequentialGuid()
{
const int RPC_S_OK = ;
Guid guid;
int result = UuidCreateSequential(out guid); if (result != RPC_S_OK)
{
throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());
}
else
{
       //这里把UuidCreateSequential函数返回的数据做处理
byte[] guidBytes = guid.ToByteArray();
Array.Reverse(guidBytes, , );
Array.Reverse(guidBytes, , );
Array.Reverse(guidBytes, , );

       //这里用时间
long timestamp = DateTime.UtcNow.Ticks / 10000L;
byte[] timestampBytes = BitConverter.GetBytes(timestamp); if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}
       //最后把时间赋值给后6位
Buffer.BlockCopy(timestampBytes, , guidBytes, , );
return new Guid(guidBytes);
} } [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid guid);

这里可以在程序调用,作为DBA在数据库使用的话可以将这个方法添加到程序集里,需要有些改动

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlTypes; public class FunctionNewGuid
{
  //这里需要添加SqlFunction属性
  //返回类型是数据库类型
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlGuid NewSequentialGuid()
{
const int RPC_S_OK = ;
Guid guid;
int result = UuidCreateSequential(out guid); if (result != RPC_S_OK)
{
throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());
}
else
{
byte[] guidBytes = guid.ToByteArray();
Array.Reverse(guidBytes, , );
Array.Reverse(guidBytes, , );
Array.Reverse(guidBytes, , ); long timestamp = DateTime.UtcNow.Ticks / 10000L;
byte[] timestampBytes = BitConverter.GetBytes(timestamp); if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}
Buffer.BlockCopy(timestampBytes, , guidBytes, , );
return new SqlGuid(guidBytes);
} } [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid guid);
}

编译生成DLL后,注册到数据库

--设置数据库是可信任
ALTER DATABASE TEST SET TRUSTWORTHY ON --创建程序集
CREATE ASSEMBLY SQLCLR FROM 'D:\SQLCLR.DLL'
WITH PERMISSION_SET = UNSAFE --用程序集方法创建函数
CREATE FUNCTION func_NewSequentialGuid()
RETURNS uniqueidentifier
AS external name SQLCLR.FunctionNewGuid.NewSequentialGuid

    

测试代码:

批量请求:


select dbo.func_NewSequentialGuid()
union
select dbo.func_NewSequentialGuid()
union
select dbo.func_NewSequentialGuid()
union
select dbo.func_NewSequentialGuid()
union
select dbo.func_NewSequentialGuid()

结果:

多次请求:


create table #t
(
uuid uniqueidentifier
,id int identity
)
go insert into #t(uuid)
values(dbo.func_NewSequentialGuid())
go 10 select * from #t

git地址

https://gitee.com/wangzhanbo/cms/tree/master/Library

如果有问题,希望大家指正。。。

SQL Server 有序GUID,SequentialGuid,的更多相关文章

  1. SQL Server报“GUID应包含带4个短划线的32位数”

    转自:http://www.seayee.net/article/info_106.html 最近在配置一台服务器的MS SQL Server 2005的维护计划自动备份数据库,能创建维护计划,但设置 ...

  2. SQL Server to MySQL

    使用 Navicat 导入向导迁移 会遇到以下问题 SQL Server 中的 GUID 类型字段会变成 {guid} 多个外层花括号, 导致程序问题. 部分字段类型长度不大一致, 需要手工调整. . ...

  3. [O]SQL SERVER下有序GUID和无序GUID作为主键&聚集索引的性能表现

     背景 前段时间学习<Microsoft SQL Server 2008技术内幕:T-SQL查询>时,看到里面关于无序GUID作为主键与聚集索引的建议,无序GUID作为主键以及作为聚集索引 ...

  4. SQL SERVER下有序GUID和无序GUID作为主键&聚集索引的性能表现

     背景 前段时间学习<Microsoft SQL Server 2008技术内幕:T-SQL查询>时,看到里面关于无序GUID作为主键与聚集索引的建议,无序GUID作为主键以及作为聚集索引 ...

  5. SQL Server中的GUID

    GUID(Global unique identifier)全局唯一标识符,它是由网卡上的标识数字(每个网卡都有唯一的标识号)以及 CPU 时钟的唯一数字生成的的一个 16 字节的二进制值. GUID ...

  6. SQL Server中字符串转化为GUID的标量函数实现

        还是工作中遇到的需求,有时候和外部的系统对接,进行数据的核对功能,外部的系统有时候主键字段列数据类是UNIQUEIDENTIFER(GUID)类型的字符串格式,去除了GUID格式中的分隔符“- ...

  7. 根据SQL Server排序规则创建顺序GUID

    public static class GuidUtil { , , , , , , DateTimeKind.Utc).Ticks / 10000L; /// <summary> /// ...

  8. 用sql server的sql语句算一个empty GUID

    在C#中得到一个empty GUID的方法是: Guid id= Guid.Empty; 那么在SQL Server Management Studio中怎样得到一个empty GUID呢? 方法有两 ...

  9. SQL Server GUID 数据迁移至MongoDB后怎样查看?

    关键字:SQL Server NEWID():BSON:MongoDB UUID 1.遇到的问题和困惑 SQL Server中的NEWID数据存储到MongoDB中会是什么样子呢?发现不能简单的通过此 ...

随机推荐

  1. Android表格布局之设置边框

    Android表格布局本身没有边框,不过可以通过背景色的设置可以实现表格边框的显示. 首先可以设置TableRow的背景色,然后设置内容的背景色.根据它们的颜色差就出现了边框.只要微调Content与 ...

  2. unity制作人物残影-绘制的方法

    这里是利用skinnedMeshRenderer原理做的 所以脚本需要挂在带这个组件的模型上 模型shader 必须要有个_Color参数属性,并且这个值可以调节颜色,会改变人物整体的透明度 [代码下 ...

  3. java ee的map

  4. mysql 5.6 windows7 解压缩版安装的坑

    从官网下载了解压缩版的mysql ,解压缩后,配置好环境变量,运行安装命令,提示我 缺失ddl文件,然后百度,找到了一个windows 系统组件扫描安装缺失组件的程序,然后继续安装,遇到了 初始化密码 ...

  5. Mybatis基本介绍

    Mybatis介绍 1.Mybatis介绍   MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了goog ...

  6. h5页面ios,双击向上滑动,拖拽到底部还能继续拖拽(露出黑色背景)

    h5页面ios,双击向上滑动,拖拽到底部还能继续拖拽 标签: 手机 2016-02-02 18:09 696人阅读 评论(0) 收藏 举报   在ios下,双击屏幕某些地方,滚动条会自动向上走一段. ...

  7. Apache 反向代理 丢失Authorization

    我后端API的服务器是Tomcat,而后端API验证是通过存放在头部Authorization的token值进行验证的. 我在测试Apache作为前端html解析的服务器时, 利用反向代理,把Api请 ...

  8. C#语言-07.文件操作

    a. 文件操作:适用于相对简单的数据保存 i. 读写文件的步骤: . 创建文件流 . 创建读写器 . 读写文件 . 关闭读写器 . 关闭文件流 ii. FileStream(文件流),它主要用于读写文 ...

  9. instanceof -- JS

    在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 “object”. ...

  10. ajax上传数据

    ---恢复内容开始--- ajax上传数据,(简洁版) 1.上传普通同表单标签内容. 1.获取表单的内容 1. var file=$('#file').val();(放在点击事件后面) 2. var ...