问题描述


有序的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. Linux 构建ftp服务器

    1.安装vsftpd服务器 $sudo apt-get install vsftpd 2.cd 到etc文件,配置vsftpd.conf文件 $sudo vi /etc/vsftpd.conf 修改至 ...

  2. 爬虫--requeste

    1.requeste模块,是我们Python对我们爬虫有好的一面,,其主要作用是用来模拟浏览器发起请求.功能强大,用法简洁高效.在爬虫领域中占据着半壁江山的地位.没有的话直接pip3 install  ...

  3. CommandBehavior.CloseConnection有何作用

    其用在ExecuteReader(c)中,返回对象前不能关闭数据库连接,须用CommandBehavior.CloseConnection: 这是一个关于实际知识点的问题,面试官考查的是应聘者数据库访 ...

  4. 国内Windows系统go get语言包

    这时候我们需要设置代理.代理工具我推荐用 lantern https://github.com/getlantern/lantern 需要注意的是,它的代理地址是: http://127.0.0.1: ...

  5. BAE+Python+Django+Wechatpy+Baidu weather api +微信订阅号 = 实现微信查询天气

    最近想在微信上面实现天气查询,分两个步骤: 1.被动回复:输入天气.xx天气获取天气情况 2.主动推送:每天定时推送天气(针对24小时内产生交互的人) 目前已经实现第一个步骤,如下: 现将实现此功能环 ...

  6. Robot Framework(用户关键字)

    在 Robot Framework 中关键字的创建分两种:系统关键字和用户关键字.系统关键字需要通过脚本开发相应的类和方法,这个我们将在后面的章节介绍.用户关键字的创建就要简单得多,它主要利用现有的系 ...

  7. FFmpeg的H.264解码器源代码简单分析

    本文简单记录FFmpeg中libavcodec的H.264解码器(H.264 Decoder)的源代码.这个H.264解码器十分重要,可以说FFmpeg项目今天可以几乎“垄断”视音频编解码技术,很大一 ...

  8. C++11并发编程:多线程std::thread

    一:概述 C++11引入了thread类,大大降低了多线程使用的复杂度,原先使用多线程只能用系统的API,无法解决跨平台问题,一套代码平台移植,对应多线程代码也必须要修改.现在在C++11中只需使用语 ...

  9. MySQL操作汇总

    这学期学习了数据库系统原理,做了实验,mark一记. T—SQL的简单查询.连接查询 (1)用SQL语句建库.建表并插入记录. 建库: 建表: 插入记录: 查看 (2)修改表结构,包括修改属性列的数据 ...

  10. Go语言备忘录(2):反射的原理与使用详解

    本文内容是本人对Go语言的反射原理与使用的备忘录,记录了关键的相关知识点,以供翻查. 文中如有错误的地方请大家指出,以免误导!转摘本文也请注明出处:Go语言备忘录(2):反射的原理与使用详解,多谢! ...