--接下来的数据库语句操作都是一些简单的命令
--数据库为Test
USE Test
GO
--创建一张table
CREATE TABLE t1
(
t1_id INT NOT NULL
)
GO
--查看t1的使用情况
sp_spaceused t1
USE Test
GO
--sys.indexes是系统视图,当创建一张新表的时候,就会在sys.indexes中增加一条记录
--可以在http://msdn.microsoft.com/zhcn/library/ms173760(v=sql.100).aspx 中找到相关信息
SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID('t1')
GO
--sys.sysindexes同样是系统视图
--可以在http://technet.microsoft.com/zh-cn/library/ms190283(v=sql.100).aspx 中找到相关信息
SELECT * FROM sys.sysindexes WHERE id = OBJECT_ID('t1')
GO
sys.indexes中的记录type_desc明确指出了索引的类型是堆(HEAP)。
sys.sysindexes中的记录与数据页组织有莫大的关系,后面会继续讨论。indid=0表示这是堆。堆只需考虑FirstIAM的变化情况,可以看到这里FirstIAM的指针地址为0。
USE Test
--接下来,插入一条记录
INSERT INTO t1(t1_id) VALUES (1)
GO
--重新查看内存使用情况
sp_spaceused t1
GO
这里可以明显地知道,数据插入的基本单位是数据页,即使插入的是一行数据,也占据一张数据页。后续插入数据的时候,如果当前的数据页还可以继续容纳的话,就插入到当前数据页中,不然就插入到一张新数据页中。但这里还是有一个疑问,为什么会有index_size呢?这里不是只插入了一条记录而已吗?这就要从堆的内部结构说起了。
USE Test
GO
--我们来继续查看sys.sysindexes,看下这条记录发生了什么变化
SELECT * FROM sys.sysindexes WHERE id = OBJECT_ID('t1')
GO
这里的FirstIAM的指针地址发生了变化。
USE Test
GO
DECLARE @i INT;
SET @i = 10000;
--插入10000条记录
WHILE @i>0
BEGIN
INSERT INTO t1 (t1_id) VALUES (@i)
SET @i = @i - 1
END
GO
--再次查看页的基本信息
DBCC IND(Test,t1,-1)
GO
插入了10000条记录后,可以看到数据页增加了17页,而IAM页还是只有1页,这是因为IAM页最多可定位4GB的数据量。在堆结构中,PrevPagePID和NextPagePID都是0,数据页之间不存在链表关系,数据页的关系仅靠IAMPID维持着。
堆结构的查询示意图如下: 3. 堆结构全表扫描
SQL-Server在接到查询请求后,便会首先分析sys.sysindexes的索引标识符indid,堆结构的indid为0,这时就会查找另一个字段FirstIAM,找到IAM页链,便开始所有数据页的依次遍历查找过程。堆结构的表就像一个存放着乱七八糟的书而且没有排序好的书库,当要查询某一类型的书或某个范围内的书的时候,就只能从第一个书架开始找起,每一本书都要看,如果匹配就拿出来,直到最后一个书架都找完了。当书库的书成千上万的时候,这样的查找方式确实效率低下。
USE Test
GO
--创建一张表
CREATE TABLE t2
(
t2_id INT IDENTITY(1,1) NOT NULL,
t2_c1 VARCHAR(10) NOT NULL
)
--创建一个在列t2_id上的聚集索引
CREATE CLUSTERED INDEX ix_t2_id
ON t2 (t2_id ASC)
--插入4000行数据
DECLARE @i INT
SET @i = 4000
WHILE @i>0
BEGIN
INSERT INTO t2 (t2_c1) VALUES ('a')
SET @i = @i - 1
END
--查看sys.sysyindexes的使用情况
SELECT * FROM sys.sysindexes WHERE id = OBJECT_ID('t2')
--使用DBCC IND查看页的使用情况
DBCC IND(Test,t2,-1)
聚集索引
indid=1表示这是一个聚集索引,与堆结构的FirstIAM转换规则一样,0X730000000100可以转化为(1:115),则指向的索引页的文件编号是1,页编号是115。
PagePID=115的行的PageType=2,表示这是一个索引页。IndexLevel表示索引的等级,数值越大表示离根节点越近。这里因为只插入了4000条数据,数据量较少,所以只需一个索引页。同时观察一下PageType=1的数据页,NextPagePID和PrePagePID将数据页串联成一个数据链表,和堆结构的数据页是有明显的区别的。这是B+树的一个特点所在,在此对B+树的结构就不多加讨论了。
可以看到建立了聚集索引的表也有一个IAM页,个人推测这个IAM页的作用是当删除聚集索引后,表变成了堆结构,这时就按堆结构的工作方式查询数据。
USE Test
GO
--这里可以使用PAGE命令查看页的具体情况
--参数(数据库名,FileID,PageID,3表示查看索引页信息)
DBCC PAGE(Test,1,115,3)
可以看到,索引页共有10行,分别指向了10个页面,t2_id是索引键,ChlidPageId是指向数据页的页编号。
t2表的B+树结构如图:
这里因为插入的每一行数据都占据相同的大小,所以数据页呈规律递增,但实际应用情况下,插入的数据基本是不同的,就不会像这里一样出现规律性的递增了。当要查询t2_id=800的记录的时候,就在索引页中查找,由于405 0
BEGIN
--设置插入字符个数
SET @num = CAST(RAND()*1000 AS INT)% 10 + 1
--字符串置空
SET @str = ''
--插入随机字符
WHILE @num > 0
BEGIN
SET @str += CHAR(CAST(RAND()*1000 AS INT)% 26 + 97)
SET @num -= 1
END
--插入t3表中
INSERT INTO t3 (t3_c1) VALUES (@str)
SET @i -= 1
END
--查看页的使用情况
DBCC IND(Test,t3,-1)
推上的聚集索引
PageType=2表示这是非聚集索引。IndexLevel=1表示这是根节点页。
USE Test
GO
--创建表t4 表结构与t3类似
CREATE TABLE t4
(
t4_id INT IDENTITY(1,1) NOT NULL,
t4_c1 VARCHAR(10) NOT NULL
)
--建立聚集索引
CREATE CLUSTERED INDEX ix_t4_clus
ON t4 (t4_id)
GO
--建立非聚集索引
CREATE NONCLUSTERED INDEX ix_t4_c1_nonclus
ON t4 (t4_c1)
GO
USE Test
GO
--插入1000行数据
DECLARE @i INT
DECLARE @num INT
DECLARE @str VARCHAR(10)
SET @i = 1000
WHILE @i > 0
BEGIN
--设置插入字符个数
SET @num = CAST(RAND()*1000 AS INT)% 10 + 1
--字符串置空
SET @str = ''
--插入随机字符
WHILE @num > 0
BEGIN
SET @str += CHAR(CAST(RAND()*1000 AS INT)% 26 + 97)
SET @num -= 1
END
--插入t3表中
INSERT INTO t4 (t4_c1) VALUES (@str)
SET @i -= 1
END
--查看页的使用情况
DBCC IND(Test,t4,-1)
聚集表上的非聚集索引
我们可以看到IndexID=1,PageType=2,IndexLevel=1的索引页就是根聚集索引页,而IndexID=2,PageType=2,IndexLevel的索引页就是根非聚集索引页。