索引键的唯一性(3/4):唯一聚集索引上的唯一和非唯一非聚集索引

原文:索引键的唯一性(3/4):唯一聚集索引上的唯一和非唯一非聚集索引

在上篇文章里,我讨论了唯一和非唯一聚集索引的区别。我们已经知道,SQL Server内部使用4 bytes的uniquifier来保证非唯一聚集索引行唯一。今天我们来看下唯一聚集索引上,唯一和非唯一非聚集索引的区别。当我们在表上定义PRIMARY KEY约束时,SQL Server会为我们创建唯一聚集索引;另外我们可以通过CREATE UNIQUE CLUSTERED INDEX语句在表上创建唯一聚集索引。下面的代码会创建customers表,然后在它上面创建唯一聚集索引,最后在表上创建唯一和非唯一非聚集索引。

 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,
 6     CustomerName CHAR(100) NOT NULL,
 7     CustomerAddress CHAR(100) NOT NULL,
 8     Comments CHAR(189) NOT NULL
 9 )
10 GO
11
12 -- Create a unique clustered index on the previous created table
13 CREATE UNIQUE CLUSTERED INDEX idx_Customers ON Customers(CustomerID)
14 GO
15 -- Insert 80.000 records
16 DECLARE @i INT = 1
17 WHILE (@i <= 80000)
18 BEGIN
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     SET @i += 1
27 END
28 GO
29
30 -- Create a unique non clustered index on the clustered table
31 CREATE UNIQUE NONCLUSTERED INDEX idx_UniqueNCI_CustomerID
32 ON Customers(CustomerName)
33 GO
34
35 -- Create a non-unique non clustered index on the clustered table
36 CREATE NONCLUSTERED INDEX idx_NonUniqueNCI_CustomerID
37 ON Customers(CustomerName)
38 GO

在2个非聚集索引创建后,我们可以使用DMV sys.dm_db_index_physical_stats来查看索引的相关信息。

 1 -- Retrieve physical information about the unique non-clustered index
 2 SELECT * FROM sys.dm_db_index_physical_stats
 3 (
 4     DB_ID('ALLOCATIONDB'),
 5     OBJECT_ID('Customers'),
 6     2,
 7     NULL,
 8     'DETAILED'
 9 )
10 GO
11
12 -- Retrieve physical information about the non-unique non-clustered index
13 SELECT * FROM sys.dm_db_index_physical_stats
14 (
15     DB_ID('ALLOCATIONDB'),
16     OBJECT_ID('Customers'),
17     3,
18     NULL,
19     'DETAILED'
20 )
21 GO

我们可以看到,唯一非聚集索引的记录长度是107 bytes,非唯一非聚集索引的记录长度是111 bytes。因此这2个索引的内部存储格式肯定不同。我们从唯一非聚集索引开始分析。

我们可以通过DBCC IND命令找出索引根页,聚集索引的INDEX ID为1,非聚集索引的INDEX ID从2开始,依次递增,这里应该是2和3。

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

从这里我们可以看出,唯一非聚集索引的根页是20834,非唯一非聚集索引的根页是21890。

我们看下唯一非聚集索引的根页内容:

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

从图中我们可以看到,每条索引记录包含非聚集键(这里是唯一的)——即CustomerName列。

我们换参数1再来看看根页信息:

1 DBCC TRACEON(3604)
2 DBCC PAGE(ALLOCATIONDB, 1, 20834, 1)
3 GO

这里的107 bytes包含下列信息:

  • 1 byte: 状态位
  • n bytes:非唯一聚集索引键——这里是CustomerName列,100 bytes
  • 4 bytes:页ID(PageID)
  • 2 bytes:文件ID(FileID)

在唯一非聚集索引里,所有非叶子层的每条索引记录都包含这107 bytes信息。因此,你的非聚集索引键大小会影响到每个索引页可以存储多少条索引记录。这样的话,这个例子的CHAR(100),并不是一个很好的索引键。

我们继续往下看,索引叶子层的存储情况:

1 DBCC PAGE(ALLOCATIONDB, 1, 20834, 3)--根层
2 GO
3
4 DBCC PAGE(ALLOCATIONDB, 1, 20833, 3)--中间层
5 GO
6
7 DBCC PAGE(ALLOCATIONDB, 1, 21098, 3)--叶子层
8 GO

从图中我们可以看到,SQL Server在叶子层这里保存聚集键(即CustomerID列的值)。这个值是SQL Server用来指向聚集索引里对应记录的指针。手上有了这个值,SQL Server就可以在聚集索引找到对应记录——通过聚集索引查找(Clustered Index Seek)运算符。这和在堆表上定义的非聚集索引有重大区别。因为在堆表里,SQL Server使用叶子层的HEAP RID直接指向数据页里存储的对应记录。因此,SQL Server不用访问额外索引,就可以直接正确读取到数据页。

这也意味着SQL Server在堆表上通过非聚集索引找记录,比在聚集表上通过非聚集索引找记录快很多,因为SQL Server不需要执行额外的聚集索引查找(Clustered Index Seek)运算符。因此在堆表上可以读取更少的页正确找到记录。当不要高估这个细节,想想在堆表通过使用非聚集索引,性能上可以有多少好处。事实上,SQL Server总是尽量把索引页放在缓存区管理器里,因此对于SQL Server来说,使用额外的聚集索引查找(Clustered Index Seek)从聚集索引里找回记录,成本更低。

现在我们来分析下非唯一非聚集索引。先来看看根页:

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

从上图可以看出,非唯一非聚集索引根页里,SQL Server这里保存里非聚集索引键和聚集索引键,这个和刚才的唯一聚集索引根页是不一样的。

SQL Server这里需要使用唯一聚集键来使非唯一非聚集索引键唯一。这在非唯一非聚集索引的每一层都会保存,从索引根页到叶子层。这就是说你需要更多的存储空间来保存索引,因为SQL Server在每条索引记录里不仅保存唯一聚集键,也保存非唯一非聚集索引键。因此当你选择不好的聚集键(像 CHAR(100)等)时,情况会变得更糟。

1 DBCC PAGE(ALLOCATIONDB, 1, 21890, 3)--根层
2 GO
3
4 DBCC PAGE(ALLOCATIONDB, 1, 21889, 3)--中间层
5 GO
6
7 DBCC PAGE(ALLOCATIONDB, 1, 22087, 3)--叶子层
8 GO

我们换参数1再来看看根页信息:

1 DBCC TRACEON(3604)
2 DBCC PAGE(ALLOCATIONDB, 1, 21890, 1)
3 GO

这111 bytes包括:

  • 1 byte:状态位
  • n bytes:非唯一非聚集索引键——这里是CustomerName列,100 bytes
  • n bytes:唯一聚集索引键——这里是CustomerID列,4 bytes
  • 4 bytes:页ID(PageID)
  • 2 bytes:文件ID(FileID)

当你把这些字节长度汇总后,你就得到了刚才提到的111 bytes。因此在你创建非唯一非聚集索引时,就要考虑到这些额外存储,因为它会影响到你的非聚集索引的每一层。

在这个系列的下篇文章里,我们最后来看下在非唯一聚集索引上,唯一和非唯一非聚集索引的区别,请继续关注!

时间: 2024-07-29 18:04:11

索引键的唯一性(3/4):唯一聚集索引上的唯一和非唯一非聚集索引的相关文章

索引键的唯一性(1/4):堆表上的唯一与非唯一非聚集索引的区别

原文:索引键的唯一性(1/4):堆表上的唯一与非唯一非聚集索引的区别 在这篇文章里,我想详细介绍下SQL Server里唯一与非唯一非聚集索引的区别.看这个文章前,希望你已经理解了聚集和非聚集索引的概念,还有在SQL Server里是如何使用的. 很多人对唯一和非唯一索引非聚集索引的认识都不是很清晰.事实上,SQL Server在存储上这2类索引有着本质的区别,这些区别会影响到索引占用空间的大小和索引的使用效率. 今天我们从SQL Server里的堆表(Heap table) ,它是没有聚集索引

索引键的唯一性(2/4):唯一与非唯一聚集索引

原文:索引键的唯一性(2/4):唯一与非唯一聚集索引 在上一篇文章里,我们讨论了堆表上唯一/非唯一非聚集索引.在SQL Server里没有聚集索引定义的叫堆表.当你在堆表上定义了一个聚集索引,你的表数据就会重组按聚集键的顺序进行物理存储,因为这个表叫做聚集表.这篇文章里,我想谈下唯一和非唯一聚集索引之间的区别,这2类聚集索引对存储的影响. 看这个文章之前,希望你对聚集索引有个基本的认识,并且知道堆表和聚集表之间的区别,还有当在表上定义了一个聚集索引,表里数据页是如何组织的(B树结构). 我们从唯

索引键的唯一性(4/4):非唯一聚集索引上的唯一和非唯一非聚集索引

原文:索引键的唯一性(4/4):非唯一聚集索引上的唯一和非唯一非聚集索引 在上一篇文章里,我谈了唯一聚集索引上的唯一和非唯一非聚集索引的区别.在这篇文章里,我想谈下非唯一聚集索引上的唯一和非唯一聚集索引的区别.我们都知道,SQL Server内部把非唯一聚集索引当作唯一聚集索引处理的.如果你定义了一个非唯一聚集索引,SQL Server会增加叫做uniquifier到你的索引记录,它导致你聚集索引的导航结构(B树的非叶子层)里,每条索引行都要用到4 bytes的开销. 下列代码再次创建我们的Cu

从性能的角度谈SQL Server聚集索引键的选择

简介 在SQL Server中,数据是按页进行存放的.而为表加上聚集索引后,SQL Server对于数据的查找就是按照聚集索引的列作为关键字进行了.因此对于聚集索引的选择对性能的影响就变得十分重要了.本文从旨在从性能的角度来谈聚集索引的选择,但这仅仅是从性能方面考虑.对于有特殊业务要求的表,则需要按实际情况进行选择. 聚集索引所在的列或列的组合最好是唯一的 这个原因需要从数据的存放原理来谈.在SQL Server中,数据的存放方式并不是以行(Row)为单位,而是以页为单位.因此,在查找数据时,S

第十二章——SQLServer统计信息(2)——非索引键上统计信息的影响

原文:第十二章--SQLServer统计信息(2)--非索引键上统计信息的影响 前言:         索引对性能方面总是扮演着一个重要的角色,实际上,查询优化器首先检查谓词上的统计信息,然后才决定用什么索引.一般情况下,默认会在创建索引时,索引列上均创建统计信息.但是不代表在非索引键上的统计信息对性能没有用.         如果表上的所有列都有索引,那么将会是数据库负担不起,同时也不是一个好想法,包括谓词中用到的所有列加索引同样也不是好方法.因为索引会带来负载.因为需要空间存放索引,且每个D

[20160711]索引键值在Btree索引块中的顺序3

[20160711]索引键值在B tree索引块中的顺序3.txt --上午测试索引键值在B tree索引块中的顺序,许多人认为是有序,主要是插入后再建立索引. --这样看到索引块里面的键值就是有序的. --今天测试一下,如果索引分裂后是否会排序呢?索引分裂有两种情况,前面测试leaf node 50-50 splits的情况. --继续测试leaf node 90-10 splits的情况. 测试看看. 1.环境: SCOTT@test01p> @ ver1 PORT_STRING      

[20160908]唯一索引与非唯一索引.txt

[20160908]唯一索引与非唯一索引.txt --唯一索引与非唯一索引的区别在于rowid信息在索引的位置,唯一索引rowid在row header(数据部分).而非唯一索引在最后. --但是具体的内部结构oracle如何识别呢?做一个简单探究,通过例子来说明: 1.环境: SCOTT@book> @ &r/ver1 PORT_STRING                    VERSION        BANNER ------------------------------ --

[20160711索引键值在B tree索引块中的顺序2

[20160711]索引键值在B tree索引块中的顺序2.txt --上午测试索引键值在B tree索引块中的顺序,许多人认为是有序,主要是插入后再建立索引. --这样看到索引块里面的键值就是有序的. --今天测试一下,如果索引分裂后是否会排序呢?索引分裂有两种情况,先测试leaf node 50-50 splits的情况. 测试看看. 1.环境: SCOTT@test01p> @ ver1 PORT_STRING                    VERSION        BANNE

[20111223]索引键值在B tree索引块中的顺序.txt

[20111223]索引键值在B tree索引块中的顺序.txt 参考链接:http://www.adellera.it/blog/2009/05/24/order-keys-inside-index-blocks/ 自己为了加强理解重复一下对方的测试! 1.建立测试表以及索引 SQL> select * from v$version; BANNER -------------------------------------------------------------------------