我要解决的问题是:
1 - 在我们的数据库中,所有表(包括数百万条记录的表)都声明了一个PK id列,类型为VARCHAR(36)。它还有一个聚簇索引。根据我在网上读到的信息,这对性能来说是非常糟糕的,因为数据库有很多读取、插入、更新和删除操作。
2 - 我们在java web应用程序中使用Hibernate作为ORM连接到这个数据库。
经过大量在线阅读后,我开始将这些列的数据类型更改为UNIQUEIDENTIFIER,并使用默认选项newsequentialid(),因为这个选项应该减轻索引的碎片问题。
但我注意到,碎片问题仍然存在,表在重建后不久就变得严重碎片化了(我们每晚进行完整的索引重建)。
接着我发现,我们所有的id列的Hibernate映射都包含了这个内容:
当我们的系统发生插入操作时,日志显示在调用
因此,在另一次在线搜索后,我尝试通过自己实现 Hibernate 中的 GUID 生成器来解决问题,实现接口
生成(我认为是顺序的)ID 的代码如下:
这对我来说看起来是按字母顺序排列的,但不是二进制顺序。
上述测试执行了七次测试应用程序,而不是循环。
我尝试将这些值插入声明为唯一标识符的列中,然后在此列上发出选择后,这是sql服务器输出的列表。
运行 5
你可以注意到所有行都以字符串
经过2000次插入测试,主键索引碎片化从17.92%降至0.15%。
注:我重新引入的数据类型显然又是varchar(36),而不是uniqueidentifier,所以行按字母顺序排序。
1 - 在我们的数据库中,所有表(包括数百万条记录的表)都声明了一个PK id列,类型为VARCHAR(36)。它还有一个聚簇索引。根据我在网上读到的信息,这对性能来说是非常糟糕的,因为数据库有很多读取、插入、更新和删除操作。
2 - 我们在java web应用程序中使用Hibernate作为ORM连接到这个数据库。
经过大量在线阅读后,我开始将这些列的数据类型更改为UNIQUEIDENTIFIER,并使用默认选项newsequentialid(),因为这个选项应该减轻索引的碎片问题。
但我注意到,碎片问题仍然存在,表在重建后不久就变得严重碎片化了(我们每晚进行完整的索引重建)。
接着我发现,我们所有的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>
然后我尝试生成一些测试uuid来测试它们的顺序性(按照唯一标识符的方式,因此是二进制),这是一个简短的列表(每个元素都在后续之前生成):
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服务器输出的列表。
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,所以行按字母顺序排序。
id
列时才会使用。由于 Hibernate 生成 GUID 并在 INSERT 中提供它,因此您获得的是该值,而忽略了您的默认值。对于 IDENTITY 列,我认为 Hibernate 有一个特定的生成器,将不包括该列在 INSERT 语句中,而是检索服务器生成的值。 - Matt Gibson