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

标签 java sql-server hibernate guid clustered-index

我要解决的问题是:

1 - 在我们的数据库中,我们拥有所有表(也包括具有数百万条记录的表),其 PK id 列声明为 VARCHAR(36)。 上面还有一个聚集索引当然,正如我在网上看到的那样,这对性能来说是一件很糟糕的事情,还因为数据库有很多读取、插入、更新和删除操作。

2 - 我们将 Hibernate 用于我们的 java web 应用程序作为此数据库的 ORM

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

我注意到碎片问题仍然存在,重建后表变得严重碎片化(我们每晚都重建完整索引)。

然后我看到我们所有的 id 列的 Hibernate 映射都包含以下内容:

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

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

所以在另一次在线搜索之后,我尝试自己在 Hibernate 中实现一个 guid 生成器,实现接口(interface) 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>

然后我尝试生成一些测试 uuid 来测试它们的顺序(以 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

这在我看来是按字母顺序排列的,而不是二进制顺序的。

上面的测试执行了七次测试应用程序,它不是一个循环。

我试图将这些值插入声明为唯一标识符的列中,并在对该列发出选择后,这是 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

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

我错过了什么?

*************************** 编辑 - 我的实现说明 ********* *********

在收到各种评论和回答后,我实现了以下策略:

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

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 键或顺序键,但在正常情况下,您不能同时拥有这两种键。

那么,您确定 key 必须是 GUID 吗?就个人而言,我发现 GUID 很难使用。

但是如果您必须进行任何必要的修改以获得连续的 GUID,那么我的建议是编写您自己的函数来生成 36 个字符的字符串,如下所示GUID,但是是连续的。

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

您可以阅读 IETF's UUID specification关于如何正确构建 GUID,但您不必一字不差地遵循它。在大多数情况下,如果它只是看起来像一个 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 - 使用 SQL Server uniqueidentifier 在 Java 中生成顺序 GUID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32758726/

相关文章:

java - 在同一 Activity 中从工具栏搜索 View 获取查询字符串?

spring - 使用 Spring Boot 框架对基于 Spring JPA 的 DAO 进行分层的正确方法

java - 如何使用 PDFBox 在 PDF 中查找空白页?

java - 带有 PropertyPlaceholderConfigurer bean 的 Spring @Configuration 文件无法解析 @Value 注释

sql - 替换SQL中的多个字符

sql - 根据具有名称列名称的另一个表更新表中 100 列行的 SQL

java - hibernate key '7090' 的重复条目 'PRIMARY'

使用 Hibernate : validation not happening on update 进行 Spring Bean 验证

java - 服务 android 中的 android.os.deadobjectexception

sql-server - 如何在 SQL Server 中使用 RANK()