我们有一个将数据存储在SQL Server数据库中的应用程序。 (目前我们支持SQL Server 2005及更高版本)。我们的DB有超过400个表。数据库的结构并不理想。最大的问题是我们有很多带有GUID(NEWID())的表作为主CLUSTERED键。当我问我们的主数据库架构师“为什么?”时,他说:“这是因为复制”。我们的数据库应该支持事务复制。最初,所有主键都是INT IDENTITY(1,1)CLUSTERED。但后来在复制支持时,这些字段被UNIQUEIDENTIFIER DEFAULT NEWID()取代。他说“否则处理复制是一场噩梦”。当时SQL 7/2000不支持NEWSEQUENTIALID()。所以现在我们有了具有以下结构的表:
CREATE TABLE Table1(
Table1_PID uniqueidentifier DEFAULT NEWID() NOT NULL,
Field1 varchar(50) NULL,
FieldN varchar(50) NULL,
CONSTRAINT PK_Table1 PRIMARY KEY CLUSTERED (Table1_PID)
)
GO
CREATE TABLE Table2(
Table2_PID uniqueidentifier DEFAULT NEWID() NOT NULL,
Table1_PID uniqueidentifier NULL,
Field1 varchar(50) NULL,
FieldN varchar(50) NULL,
CONSTRAINT PK_Table2 PRIMARY KEY CLUSTERED (Table2_PID),
CONSTRAINT FK_Table2_Table1 FOREIGN KEY (Table1_PID) REFERENCES Table1 (Table1_PID)
)
GO
所有表实际上都有很多字段(最多35个)和最多15个非聚集索引。
我知道GUID不是顺序的 - 比如在客户端(使用.NET)生成它的值,或者由NEWID()SQL函数生成的GUID(就像我们的情况一样)是一个非常糟糕的选择聚集索引有两个原因:
我也知道好的聚类关键是:
有关此背后原因的详细信息,请查看以下精彩视频:http://technet.microsoft.com/en-us/sqlserver/gg508879.aspx。
所以,INT IDENTITY确实是最好的选择。 BIGINT IDENTITY也很好,但是对于绝大多数表来说,通常一个行数超过20亿的INT就足够了。
当我们的客户开始遭受碎片化时,决定将主键设置为非群集。结果,这些表保持没有聚簇索引。换句话说,这些表格变成了HEAPS。我个人不喜欢这个解决方案,因为我确信堆表不是一个好的数据库设计的一部分。请检查此SQL Server最佳实践文章:http://technet.microsoft.com/en-us/library/cc917672.aspx。
目前我们考虑两种改进数据库结构的方法:
第一个选项是用主要群集密钥的DEFAULT NEWSEQUENTIALID()替换DEFAULT NEWID():
CREATE TABLE Table1_GUID ( Table1_PID uniqueidentifier DEFAULT NEWSEQUENTIALID() NOT NULL, Field1 varchar(50) NULL, FieldN varchar(50) NULL, CONSTRAINT PK_Table1 PRIMARY KEY CLUSTERED (Table1_PID) ) GO
第二个选项是将INT IDENTITY列添加到每个表,并使其成为CLUSTERED UNIQUE索引,使主键不进行聚类。所以Table1看起来像:
CREATE TABLE Table1_INT ( Table1_ID int IDENTITY(1,1) NOT NULL, Table1_PID uniqueidentifier DEFAULT NEWSEQUENTIALID() NOT NULL, Field1 varchar(50) NULL, FieldN varchar(50) NULL, CONSTRAINT PK_Table1 PRIMARY KEY NONCLUSTERED (Table1_PID), CONSTRAINT UK_Table1 UNIQUE CLUSTERED (Table1_ID) ) GO
Table1_PID将用于复制(这就是我们将其保留为PK的原因),而Table1_ID根本不会被复制。
长话短说,在我们运行基准测试以确定哪种方法更好后,我们发现两种解决方案都不好:
第一种方法(Table1_GUID)揭示了以下缺点:虽然顺序GUID肯定比常规随机GUID好很多,但它们仍然是INT的四倍(16比4字节)和这是我们案例中的一个因素,因为我们的表中有很多行(最多6000万),并且表上有很多非聚集索引(最多15个)。聚类键被添加到每个非聚集索引中,因此显着增加了16个大小与4个字节的负面影响。更多字节意味着磁盘和SQL Server RAM中的页面越多,因此更多的磁盘I / O和更多的SQL Server工作。
更准确地说,在我向每个表插入25mln行真实数据然后在每个表上创建了15个非聚集索引之后,我看到表中使用的空间有很大差异:
EXEC sp_spaceused 'Table1_GUID' -- 14.85 GB
EXEC sp_spaceused 'Table1_INT' -- 11.68 GB
此外,测试显示INSERT到Table1_GUID比Table1_INT慢一点。
第二种方法(Table1_INT)显示在大多数查询(SELECT)中连接Table1_INT上的两个表.Table1_PID = Table2_INT.Table1_PID执行计划变得更糟,因为出现了额外的Key Lookup操作符。
现在的问题是:我相信应该有更好的解决方案来解决我们的问题。如果你可以向我推荐一些东西或者给我一个很好的资源,我会非常感激。提前谢谢。
更新:
让我举一个SELECT语句的例子,其中出现了额外的Key Lookup操作符:
--Create 2 tables with int IDENTITY(1,1) as CLUSTERED KEY.
--These tables have one-to-many relationship.
CREATE TABLE Table1_INT (
Table1_ID int IDENTITY(1,1) NOT NULL,
Table1_PID uniqueidentifier DEFAULT NEWSEQUENTIALID() NOT NULL,
Field1 varchar(50) NULL,
FieldN varchar(50) NULL,
CONSTRAINT PK_Table1_INT PRIMARY KEY NONCLUSTERED (Table1_PID),
CONSTRAINT UK_Table1_INT UNIQUE CLUSTERED (Table1_ID)
)
GO
CREATE TABLE Table2_INT(
Table2_ID int IDENTITY(1,1) NOT NULL,
Table2_PID uniqueidentifier DEFAULT NEWSEQUENTIALID() NOT NULL,
Table1_PID uniqueidentifier NULL,
Field1 varchar(50) NULL,
FieldN varchar(50) NULL,
CONSTRAINT PK_Table2_INT PRIMARY KEY NONCLUSTERED (Table2_PID),
CONSTRAINT UK_Table2_INT UNIQUE CLUSTERED (Table2_ID),
CONSTRAINT FK_Table2_Table1_INT FOREIGN KEY (Table1_PID) REFERENCES Table1_INT (Table1_PID)
)
GO
为comperison创建其他两个表:
--Create the same 2 tables, BUT with uniqueidentifier NEWSEQUENTIALID() as CLUSTERED KEY.
CREATE TABLE Table1_GUID (
Table1_PID uniqueidentifier DEFAULT NEWSEQUENTIALID() NOT NULL,
Field1 varchar(50) NULL,
FieldN varchar(50) NULL,
CONSTRAINT PK_Table1_GUID PRIMARY KEY CLUSTERED (Table1_PID),
)
GO
CREATE TABLE Table2_GUID(
Table2_PID uniqueidentifier DEFAULT NEWSEQUENTIALID() NOT NULL,
Table1_PID uniqueidentifier NULL,
Field1 varchar(50) NULL,
FieldN varchar(50) NULL,
CONSTRAINT PK_Table2_GUID PRIMARY KEY CLUSTERED (Table2_PID),
CONSTRAINT FK_Table2_Table1_GUID FOREIGN KEY (Table1_PID) REFERENCES Table1_GUID (Table1_PID)
)
GO
现在运行以下select语句并查看要比较的执行计划:
SELECT T1.Field1, T2.FieldN
FROM Table1_INT T1
INNER JOIN Table2_INT T2
ON T1.Table1_PID = T2.Table1_PID;
SELECT T1.Field1, T2.FieldN
FROM Table1_GUID T1
INNER JOIN Table2_GUID T2
ON T1.Table1_PID = T2.Table1_PID;
答案 0 :(得分:1)
我个人使用INT IDENTITY
来处理我的主要和群集密钥。
您需要将主键分开,这是一个逻辑结构 - 它唯一标识您的行,它必须是唯一且稳定的NOT NULL
。 GUID
也适用于主键 - 因为它保证是唯一的。如果使用SQL Server复制,则GUID
作为主键是一个不错的选择,因为在这种情况下,无论如何都需要唯一标识GUID
列。
SQL Server中的集群密钥是一个物理结构,用于数据的物理排序,并且更难以正确使用。通常,SQL Server上的索引女王,Kimberly Tripp,也需要一个好的聚类密钥,以便尽可能地独特,稳定,并且理想地不断增加(INT IDENTITY
GUID
是)。
请参阅她关于索引的文章:
还可以看到Jimmy Nilsson的The Cost of GUIDs as Primary Key
对于群集密钥来说,GUID
是一个非常糟糕的选择,因为它很宽,完全随机,因此会导致错误的索引碎片和糟糕的性能。此外,群集密钥行也存储在每个非群集(附加)索引的每个条目中,因此您确实希望保持较小 - INT
是16字节而不是{{ 1}}是4字节,并且有几个非聚集索引和几百万行,这会产生巨大差异。
在SQL Server中,您的主键默认情况下是您的群集密钥 - 但它不一定是。您可以轻松地使用GUID
作为非群集主键,使用INT IDENTITY
作为群集密钥 - 只需要了解一点。