唯一与非唯一聚集索引

在上一篇文章里,我们讨论了堆表上唯一/非唯一非聚集索引。在SQL Server里没有聚集索引定义的叫堆表。当你在堆表上定义了一个聚集索引,你的表数据就会重组按聚集键的顺序进行物理存储,因为这个表叫做聚集表。这篇文章里,我想谈下唯一和非唯一聚集索引之间的区别,这2类聚集索引对存储的影响。

看这个文章之前,希望你对聚集索引有个基本的认识,并且知道堆表和聚集表之间的区别,还有当在表上定义了一个聚集索引,表里数据页是如何组织的(B树结构)。

我们从唯一聚集索引谈起。在SQL Server里你有很多方法去定义唯一聚集索引。第1个最简单的方法就是列上定义一个主键(PRIMARY KEY)约束。SQL Server通过在表上创建那列的唯一聚集索引来施行主键(PRIMARY KEY)约束。另外一个方法是通过CREATE CLUSTERED INDEX语句来常见唯一聚集索引——但当你不指定UNIQUE属性时,SQL Server默认是会为你创建非唯一的聚集索引!下列这段代码会创建Customers表,这个表结构和上篇文章一样,但这次我们在CustomerID列创建主键(PRIMARY KEY)约束。因此SQL Server会在表上创建唯一聚集索引,在叶子层里,数据页是按CustomerID列值排序的。

复制代码
 1 -- Create a table with 393 length + 7 bytes overhead = 400 bytes
 2 -- Therefore 20 records can be stored on one page (8.096 / 400) = 20,24
 3 CREATE TABLE Customers
 4 (
 5    CustomerID INT NOT NULL PRIMARY KEY IDENTITY(1, 1),
 6    CustomerName CHAR(100) NOT NULL,
 7    CustomerAddress CHAR(100) NOT NULL,
 8    Comments CHAR(189) NOT NULL
 9 )
10 GO
11 
12 -- Insert 80.000 records
13 DECLARE @i INT = 1
14 WHILE (@i <= 80000)
15 BEGIN
16    INSERT INTO Customers VALUES
17    (
18       'CustomerName' + CAST(@i AS CHAR),
19       'CustomerAddress' + CAST(@i AS CHAR),
20       'Comments' + CAST(@i AS CHAR)
21    )
22 
23    SET @i += 1
24 END
25 GO
复制代码

我们可以通过DBCC IND命令找出索引根页后(PageType为2,IndexLevel为2,即B树有3层:根和叶子层,PagePID为15359),就可以使用DBCC PAGE查看根页的内容。这里我的索引根页是15359。

复制代码
1 TRUNCATE TABLE dbo.sp_table_pages
2 INSERT INTO dbo.sp_table_pages
3 EXEC('DBCC IND(ALLOCATIONDB, Customers, -1)') 
4 
5 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC
复制代码

1 DBCC PAGE(ALLOCATIONDB, 1, 15359, 3)
2 GO

从上图里,我们可以看到每个索引记录包含聚集键,在这个例子是CustomerID列的值。

如果你从字节存储级别分析聚集索引记录的话,你会发现SQL Server这里使用下列字节信息:

  • 1 byte:状态位
  • n bytes:聚集键——这个例子里是4 bytes
  • 4 bytes:页ID(PageID)
  • 2 bytes:文件ID(FileID)

可以看出,聚集键的长度直接影响索引记录的长度。这就是说,你的聚集键长度越小,索引页上就可以存放更多的索引记录,因此你的聚集索引将更紧凑,查找更快,维护更容易。当你在你的聚集索引继续往下看时,你会发现所有中间层的索引结构和刚才的描述完全一样。这2层是没有任何区别的,除了索引叶子层,因为这层包含你实际逻辑排序的数据页。

现在我们来看看SQL Server里非唯一聚集索引,看看它们和唯一聚集索引的区别。为了演示这类索引,我重建了Customers表,并通过CREATE CLUSTERED INDEX语句在表上创建了非唯一聚集索引。

复制代码
 1 DROP TABLE dbo.Customers
 2 -- Create a table with 393 length + 7 bytes overhead = 400 bytes
 3 -- Therefore 20 records can be stored on one page (8.096 / 400) = 20,24
 4 CREATE TABLE Customers
 5 ( 
 6    CustomerID INT NOT NULL,
 7    CustomerName CHAR(100) NOT NULL,
 8    CustomerAddress CHAR(100) NOT NULL,
 9    Comments CHAR(181) NOT NULL
10 )
11 GO
12 
13 -- Create a non unique clustered index
14 CREATE CLUSTERED INDEX idx_Customers_CustomerID
15 ON Customers(CustomerID)
16 GO
复制代码

最后,我插入80000条记录,这些记录的CustomerID列(聚集键)不再唯一:

复制代码
 1 -- Insert 80.000 records
 2 DECLARE @i INT = 1
 3 WHILE (@i <= 20000)
 4 BEGIN
 5    INSERT INTO Customers VALUES
 6    (
 7       @i,
 8       'CustomerName' + CAST(@i AS CHAR),
 9       'CustomerAddress' + CAST(@i AS CHAR),
10       'Comments' + CAST(@i AS CHAR)
11    )
12    INSERT INTO Customers VALUES
13    (
14       @i,
15       'CustomerName' + CAST(@i AS CHAR),
16       'CustomerAddress' + CAST(@i AS CHAR),
17       'Comments' + CAST(@i AS CHAR)
18    )
19    INSERT INTO Customers VALUES
20    (
21       @i,
22       'CustomerName' + CAST(@i AS CHAR),
23       'CustomerAddress' + CAST(@i AS CHAR),
24       'Comments' + CAST(@i AS CHAR)
25    )
26  
27    INSERT INTO Customers VALUES
28    (
29       @i,
30       'CustomerName' + CAST(@i AS CHAR),
31       'CustomerAddress' + CAST(@i AS CHAR),
32       'Comments' + CAST(@i AS CHAR)
33    )
34 
35 SET @i += 1
36 END
37 GO
复制代码

我们找下这个非唯一聚集索引的根页:

复制代码
1 TRUNCATE TABLE dbo.sp_table_pages
2 INSERT INTO dbo.sp_table_pages
3 EXEC('DBCC IND(ALLOCATIONDB, Customers, -1)') 
4 
5 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC
复制代码

我们再来看看根页的内容:

1 DBCC PAGE(ALLOCATIONDB, 1, 15359, 3)
2 GO

我们发现,SQL Server这里增加了UNIQUIFIER (key)的额外列。这列是SQL Server用来保证非唯一聚集键唯一。UNIQUIFIER (key)是4 bytes始于0的长整型值。当你有2条CustomerID值都是1380时,第1条的UNIQUIFIER为0,第2条的UNIQUIFIER值为1。但SQL Server只在索引的导航结构(高于叶子层的所有层)里保存UNIQUIFIER,即叶子层的UNIQUIFIER不为0。SQL Server只在非唯一聚集索引的导航结构里包含0值的UNIQUIFIER,这就是说导航结构里是不物理保存UNIQUIFIER的。在非唯一聚集索引里,唯一保存UNIQUIFIER的地方是在数据页,就是保存实际数据的地方。下图是我们聚集聚集索引里的中间层,你会看到UNIQUIFIER在这里是保存的。

复制代码
1 DBCC PAGE(ALLOCATIONDB, 1, 15359, 3)
2 GO
3 
4 DBCC PAGE(ALLOCATIONDB, 1, 14635, 3)
5 GO
复制代码

最后我们看看数据页14633:

1 DBCC TRACEON(3604)
2 DBCC PAGE(ALLOCATIONDB, 1, 14633, 3) with tableresults
3 GO

我们来找4条CustomerID值为1的记录,看看UNIQUIFIER的值是多少(应该是0,1,2,3)。

因此唯一和非唯一聚集索引的区别是在数据页,因为当使用非唯一聚集索引时,SQL Server使用4 bytes长的UNIQUIFIER来保证它们唯一,要记住,在你定义非唯一聚集索引时,这个额外开销始终存在。


本文转自Woodytu博客园博客,原文链接:http://www.cnblogs.com/woodytu/p/4562547.html,如需转载请自行联系原作者

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
场景一: 确认1.碰到了一个一个常慢的SQL server语句,发现是变量查询时很慢 SQL语句: DECLARE @SN VARCHAR(12) SET @SN = '30F335CD0045' SELECT [Mac2] FROM SF_Cp_Detail WHERE [Mac2] = @SN 确认2.查看索引是:唯一聚集索引 CREATE UNIQUE NONCLUSTERED INDEX [IX_SF_CP_Detail_MAC2] ON [dbo].[SF_Cp_Detail] ( [Mac2] ASC ) WHERE ([MAC2]'' AND [MAC2] IS NOT NULL) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) GO 确认3.执行计划如下: 执行计划 SET STATISTICS IO ON ; (0 行受影响) Table 'SF_Cp_Detail'. Scan count 33, logical reads 1267942, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 场景二: 为什么用不到索引IX_SF_CP_Detail_MAC2]呢? 尝试1: 把唯一聚集索引改为聚集索引, Done,用到索引了. Drop index ……. CREATE NONCLUSTERED INDEX [IX_SF_Cp_Detail_Mac2] ON [dbo].[SF_Cp_Detail] ( [Mac2] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) GO 尝试2: 可是还需要验证唯一性怎么办呢? 试了半天未达到目标, 根据别人提示, 恢复唯一聚集 CREATE UNIQUE NONCLUSTERED INDEX [IX_SF_CP_Detail_MAC2] ON [dbo].[SF_Cp_Detail] ( [Mac2] ASC ) WHERE ([MAC2]'' AND [MAC2] IS NOT NULL) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) GO 然后 修改查询: DECLARE @SN VARCHAR(12) SET @SN = '30F335CD0045' SELECT [Mac2] FROM SF_Cp_Detail WHERE [Mac2] = @SN option (recompile) 预计查询计划 看样子不行, 但是,看一下实际查询计划: OK ,Done,可以了. 尝试3. 但是我不可能去每个程序加option (recompile)呀, 而且随着数据量的增大,每次重新编译索引, 本身就导致SQL语句变慢. 最终解决方案: 唯一聚集索引留着, 再添加一个 聚集索引,保留两个索引, 终于搞定了. CREATE UNIQUE NONCLUSTERED INDEX [IX_SF_CP_Detail_MAC2] ON [dbo].[SF_Cp_Detail] ( [Mac2] ASC ) WHERE ([MAC2]'' AND [MAC2] IS NOT NULL) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING =

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值