ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

使用SQL Server uniqueidentifier在Java中生成顺序GUID

2019-05-19 17:49:01  阅读:609  来源: 互联网

标签:java guid sql-server hibernate clustered-index


我想解决的问题是:

1 – 在我们的数据库中,我们拥有所有表(也包含数百万条记录的表),其中PK id列声明为VARCHAR(36).它上面还有一个聚簇索引当然,当我在线阅读时,它对于性能来说是一件糟糕的事情,也因为db有很多读取,插入,更新和删除.

2 – 我们将Java Web应用程序的Hibernate用作此数据库的ORM

在线阅读后,我开始使用默认选项newsequentialid()将这些列的数据类型更改为UNIQUEIDENTIFIER,因为此选项可以缓解索引的碎片问题.

我注意到碎片问题仍然存在,重建后表格变得非常碎片化(我们每晚都进行完整的索引重建).

然后我看到id列的所有Hibernate映射都包含:

<id name="id" column="id" type="string">
        <generator class="guid"/>
</id>

当在我们的系统中发生插入时,日志显示插入是在调用select newid()之后完成的,所以由于这会返回一个随机guid,插入将被放置在索引中的随机点,从而导致碎片(这完全打败了列数据类型更改我也做了).

因此,经过另一次在线搜索,我试图在Hibernate中实现一个guid生成器,实现接口IdentifierGenerator并使用基于时间的生成器和JUG(http://wiki.fasterxml.com/JugHome).

生成(我认为顺序)id的代码是这样的:

String uuid = null;   
EthernetAddress nic = EthernetAddress.fromInterface();    
TimeBasedGenerator uuidGenerator = Generators.timeBasedGenerator(nic);                
uuid = uuidGenerator.generate().toString();

我相应地改变了映射到这个:

<id name="id" column="id" type="string">
            <generator class="my_package.hibernate.CustomSequentialGuidGenerator">
            </generator>
</id>

然后我尝试生成一些测试uuids来测试它们的顺序性(以uniqueidentifier方式顺序,所以二进制),这是一个短列表(每个元素在连续之前生成):

314a9a1b-6295-11e5-8d2c-2c27d7e1614f
3d867801-6295-11e5-ae09-2c27d7e1614f
4434ac7d-6295-11e5-9ed1-2c27d7e1614f
491462c4-6295-11e5-af81-2c27d7e1614f
5389ff4c-6295-11e5-84cf-2c27d7e1614f
57098959-6295-11e5-b203-2c27d7e1614f
5b62d144-6295-11e5-9883-2c27d7e1614f

这看起来像按字母顺序排列,但不是二进制顺序.

上面的测试是在测试应用中执行了七次,它不是一个循环.

我试图在声明为唯一标识符的列中插入这些值,并在此列上发出select之后,这是sql server输出的列表:

5389FF4C-6295-11E5-84CF-2C27D7E1614F
314A9A1B-6295-11E5-8D2C-2C27D7E1614F
5B62D144-6295-11E5-9883-2C27D7E1614F
4434AC7D-6295-11E5-9ED1-2C27D7E1614F
3D867801-6295-11E5-AE09-2C27D7E1614F
491462C4-6295-11E5-AF81-2C27D7E1614F
57098959-6295-11E5-B203-2C27D7E1614F

所以我真的不明白我应该做什么,如果我可以使用JUG作为顺序guid生成器来避免我的碎片问题.

这是另一个JUG测试,我尝试了3次运行,每次生成10个带有循环的guid:

运行1

54bd156e-62a2-11e5-a1a7-2c27d7e1614f
54c3cc2f-62a2-11e5-a1a7-2c27d7e1614f
54caf820-62a2-11e5-a1a7-2c27d7e1614f
54d1aee1-62a2-11e5-a1a7-2c27d7e1614f
54d901e2-62a2-11e5-a1a7-2c27d7e1614f
54df9193-62a2-11e5-a1a7-2c27d7e1614f
54e64854-62a2-11e5-a1a7-2c27d7e1614f
54ecff15-62a2-11e5-a1a7-2c27d7e1614f
54f3b5d6-62a2-11e5-a1a7-2c27d7e1614f
54fa4587-62a2-11e5-a1a7-2c27d7e1614f

跑2

87c66bcc-62a2-11e5-8e7c-2c27d7e1614f
87ccd46d-62a2-11e5-8e7c-2c27d7e1614f
87d3641e-62a2-11e5-8e7c-2c27d7e1614f
87d97e9f-62a2-11e5-8e7c-2c27d7e1614f
87e05c70-62a2-11e5-8e7c-2c27d7e1614f
87e6ec21-62a2-11e5-8e7c-2c27d7e1614f
87ed7bd2-62a2-11e5-8e7c-2c27d7e1614f
87f40b83-62a2-11e5-8e7c-2c27d7e1614f
87fac244-62a2-11e5-8e7c-2c27d7e1614f
880103d5-62a2-11e5-8e7c-2c27d7e1614f

跑3

a4b690db-62a2-11e5-b667-2c27d7e1614f
a4bcd26c-62a2-11e5-b667-2c27d7e1614f
a4c2eced-62a2-11e5-b667-2c27d7e1614f
a4c92e7e-62a2-11e5-b667-2c27d7e1614f
a4cf48ff-62a2-11e5-b667-2c27d7e1614f
a4d5d8b0-62a2-11e5-b667-2c27d7e1614f
a4dc6861-62a2-11e5-b667-2c27d7e1614f
a4e34632-62a2-11e5-b667-2c27d7e1614f
a4e9d5e3-62a2-11e5-b667-2c27d7e1614f
a4f101d4-62a2-11e5-b667-2c27d7e1614f

跑4

c2b872b2-62a2-11e5-b855-2c27d7e1614f
c2c17363-62a2-11e5-b855-2c27d7e1614f
c2c82a24-62a2-11e5-b855-2c27d7e1614f
c2ce92c5-62a2-11e5-b855-2c27d7e1614f
c2d57096-62a2-11e5-b855-2c27d7e1614f
c2dc2757-62a2-11e5-b855-2c27d7e1614f
c2e32c38-62a2-11e5-b855-2c27d7e1614f
c2e9bbe9-62a2-11e5-b855-2c27d7e1614f
c2f099ba-62a2-11e5-b855-2c27d7e1614f
c2f7507b-62a2-11e5-b855-2c27d7e1614f

运行5

f0263d1b-62a2-11e5-8529-2c27d7e1614f
f02d1aec-62a2-11e5-8529-2c27d7e1614f
f033d1ad-62a2-11e5-8529-2c27d7e1614f
f03a615e-62a2-11e5-8529-2c27d7e1614f
f041181f-62a2-11e5-8529-2c27d7e1614f
f047a7d0-62a2-11e5-8529-2c27d7e1614f
f04dc251-62a2-11e5-8529-2c27d7e1614f
f05403e2-62a2-11e5-8529-2c27d7e1614f
f05a6c83-62a2-11e5-8529-2c27d7e1614f
f0608704-62a2-11e5-8529-2c27d7e1614f

运行6(从0开始)

00fd4ec3-62a3-11e5-8ab8-2c27d7e1614f
01042c94-62a3-11e5-8ab8-2c27d7e1614f
010b3175-62a3-11e5-8ab8-2c27d7e1614f
0111e836-62a3-11e5-8ab8-2c27d7e1614f
0118ed17-62a3-11e5-8ab8-2c27d7e1614f
011fcae8-62a3-11e5-8ab8-2c27d7e1614f
0126a8b9-62a3-11e5-8ab8-2c27d7e1614f
012d115a-62a3-11e5-8ab8-2c27d7e1614f
0133c81b-62a3-11e5-8ab8-2c27d7e1614f
013a30bc-62a3-11e5-8ab8-2c27d7e1614f

单个组按字母顺序(但不是二进制)排序,并且作为一个整体采用不同的运行,它们不是按字母顺序排序的事件(叹气).

我错过了什么?

*************************编辑 – 我的实施说明******************

在各种评论和答案之后,我实施了以下策略:

我生成了自己的顺序(基于当前时间戳)guids,这是生成器类:

package it.hibernate;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang.RandomStringUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.IdentifierGenerator;



public class CustomSequentialGuidGenerator implements IdentifierGenerator{


    @Override
    public Serializable generate(SessionImplementor session, Object object)
            throws HibernateException 
        {

        String uuid = null;
        try {

            Date data = new Date();

             SimpleDateFormat sdf = new SimpleDateFormat(); 
             String rand = RandomStringUtils.randomAlphanumeric(12);

             sdf.applyPattern("yyyy");
             String year = sdf.format(data);

             sdf.applyPattern("MM");
             String month = sdf.format(data);

             sdf.applyPattern("dd");
             String day = sdf.format(data);

             sdf.applyPattern("HH");
             String hour = sdf.format(data);

             sdf.applyPattern("mm");
             String mins = sdf.format(data);

             sdf.applyPattern("ss");
             String secs = sdf.format(data);

             sdf.applyPattern("SSS");
             String millis = sdf.format(data);

             //G carachter is used to insert the rows after
             uuid = "GG" + year + month + "-" + day + hour + "-" + mins + secs + "-" + "0" + millis + "-" + rand;


        } 
        catch (Exception exception) 
        {
            exception.printStackTrace();                
        }

        return uuid;
    }
}

您可以注意到所有行都以字符串’GG’开头,因为我必须确保在通过select newid()生成的所有旧行之后插入所有新行.之后有当前时间戳和12个随机字符,以避免在同一毫秒内插入多行的情况下发生冲突.

在2000年的测试之后插入主键索引碎片从17,92%下降到0.15%.

注:我重新引入的数据类型显然是varchar(36)而不是uniqueidentifier,因此行按字母顺序排序.

解决方法:

newsequentialid()的默认选项当然不起作用,因为hibernate不使用默认值,它总是设置由其生成器发出的值.

通过快速查看JUG库,它似乎没有提供任何顺序生成GUID的方法.我不知道你为什么认为通过Generators.timeBasedGenerator()获得的生成器的generate()方法会给你顺序的GUID.基于时间的生成器只是一个生成器,它在生成GUID时将当前时间考虑在内,但是当将它嵌入到GUID中时,它可以以任何方式自由地修改当前时间坐标,因此它不能保证会有关于生成的GUID的任何顺序.

通常,术语“GUID”和“顺序”彼此不兼容.您可以拥有GUID键或顺序键,但在正常情况下,您不能同时拥有这两个键.

那么,你确定密钥必须是GUID吗?就个人而言,我发现GU​​ID非常难以使用.

但是如果你必须做任何必要的hack以获得顺序GUID,那么我的建议是编写你自己的函数,它生成36个字符的字符串看起来像GUID,但是是顺序的.

顺序部分应该来自SEQUENCE,它只是发出顺序整数. (我相信MS-SQL-Server支持它们.)

您可以阅读有关如何正确构建GUID的IETF’s UUID specification,但您不必遵循该字母.在大多数情况下,如果它看起来像一个GUID,它就足够了.

如果你可以有一个单一的全局序列,这很好.如果您不能拥有单个全局序列,则需要以某种方式识别序列,然后在生成GUID时将每个序列的标识符考虑在内. (这将是IETF文档中提到的“节点ID”.)

我曾经有过无理要求,我要传输到某个Web服务的行必须由GUID识别,而且有太多的繁文缛节阻止我联系他们,问他们“你觉得这个严重吗?” “所以我只是传输了如下的GUID:

|--- random part -----| |-- key ---|
314a9a1b-6295-11e5-8d2c-000000000001
314a9a1b-6295-11e5-8d2c-000000000002
314a9a1b-6295-11e5-8d2c-000000000003
314a9a1b-6295-11e5-8d2c-000000000004
314a9a1b-6295-11e5-8d2c-000000000005
...

他们没说一句话.

标签:java,guid,sql-server,hibernate,clustered-index
来源: https://codeday.me/bug/20190519/1136659.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有