我想解决的问题是:
1 - 在我们的数据库中,我们拥有所有表(也包含数百万条记录的表),其中PK id列声明为VARCHAR(36)。 它上面还有一个聚集索引当然,当我在线阅读时,这对性能来说是一件糟糕的事情,因为db有很多读取,插入,更新和删除。
2 - 我们将Java Web应用程序的Hibernate用作此db的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%。
N.B。我重新引入的数据类型显然是varchar(36)而不是uniqueidentifier,因此行按字母顺序排序。
答案 0 :(得分:5)
newsequentialid()
的默认选项当然不起作用,因为hibernate不使用默认值,它总是设置由其生成器发出的值。
通过快速查看JUG库,它似乎没有提供任何顺序生成GUID的方法。我不知道您为什么认为通过generate()
获得的生成器的Generators.timeBasedGenerator()
方法会为您提供顺序GUID。基于时间的生成器只是一个生成器,它在生成GUID时将当前时间考虑在内,但是当将它嵌入到GUID中时,它可以以任何它认为合适的方式自由地修改当前时间坐标,所以它不保证结果GUID会有任何顺序。
一般来说,术语&#34; GUID&#34;和&#34;顺序&#34;是彼此不相容的。您可以拥有GUID键或顺序键,但在正常情况下,您不能同时拥有这两个键。
那么,你确定密钥必须是GUID吗?就个人而言,我发现GUID非常难以使用。
但是如果你 必须 做任何必要的hack以获得顺序GUID,那么我的建议就是编写自己的函数,生成36个字符的字符串,看起来像GUID,但是是顺序的。
顺序部分应来自SEQUENCE
,它只发出顺序整数。 (我相信MS-SQL-Server支持它们。)
您可以阅读有关如何正确构建GUID的IETF's UUID specification,但您不必遵循该字母。在大多数情况下,如果它只是看起来像一个GUID,那就足够了。
如果你可以有一个全局序列,这很好。如果您不能拥有单个全局序列,则需要以某种方式识别序列,然后在生成GUID时将每个序列的标识符考虑在内。 (这将是IETF文档中提到的&#34;节点ID&#34;)
我曾经有过无理要求,我要传输给某个Web服务的行必须由GUID识别,并且有太多的繁文缛节阻止我联系他们询问他们&#34;你是friggin&#39; ?严重的&#34;所以我只是传输了如下的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
...
他们没有说一句话。