索引是一種幫助查詢語句能夠快速定位到資料的一種技術。索引的儲存方式有行儲存索引、列儲存索引和記憶體優化三種儲存方式:
Bw樹使用一組新的旋轉技術,支援更加高效的範圍查詢操作。而B+樹則使用葉節點連結串列來處理範圍查詢。在B+樹中,如果您需要範圍查詢,您需要遍歷整個連結串列,這會增加查詢的時間成本。相比之下,Bw樹通過一些特殊的旋轉操作,能夠使得範圍查詢操作更加高效,從而顯著提高查詢效能。
假設需要查詢數位在100到200之間的資料,那麼B+樹需要遍歷相應的葉節點連結串列,而Bw樹則可以使用一些特殊的旋轉操作,跳過某些節點,快速定位到相應的資料範圍,從而減少了查詢的時間成本。
總體來說,Bw樹在範圍查詢和隨機操作等特殊情況下比B+樹更加高效。但是對於其他型別的查詢操作,它們的效能並沒有很大的區別,具體的效果需要根據應用場景來進行具體分析。
聚集索引和非聚集索引都是使用B+樹結構組織的,最頂層稱為根節點,中間層稱為中間節點,最底層稱為葉節點。在聚集索引中,葉節點包含了基礎表的資料頁,根節點和中間節點包含了索引行的索引頁,每個索引行包含一個鍵值和一個指標,通過指標來找到某個葉節點的資料行。而在非聚集索引中,葉節點只包含了索引行的索引頁,沒有資料頁,它的索引行中只有指標,通過指標來找到對應的堆表的RID或者聚集索引的資料頁。
聚集索引決定了表中資料行的儲存順序(升序/降序),所以每張表只能有1個聚集索引,可以使用CREATE CLUSTERED INDEX
來手動建立聚集索引,也可以是在建表時指定主鍵的方式來自動建立。
每張表可以有多個非聚集索引,可以針對不同的查詢語句和業務場景來建立非聚集索引,只能是使用CREATE NONCLUSTERED INDEX
來手動建立非聚集索引。
由於聚集索引的葉節點儲存了是資料頁,由中間節點存放了指標,而非聚集索引的葉節點存放了指標(行定位器),那通過B+樹的構造,可以大概判斷是非聚集索引要消耗的空間更多,因為非聚集索引要存放更多的指標資訊(葉節點的數量肯定會比中間節點的數量多)。
exec sp_spaceused order_line
命令檢視。ol_w_id
、ol_d_id
、ol_o_id
和ol_number
列上建立聚簇索引 order_line_i1_clustered
CREATE UNIQUE CLUSTERED INDEX [order_line_i1_clustered] ON [dbo].[order_line]
(
[ol_w_id] ASC,
[ol_d_id] ASC,
[ol_o_id] ASC,
[ol_number] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = OFF, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
order_line_i1_clustered
的大小為232KB-24KB=208KB。exec sp_spaceused order_line
命令檢視。order_line_i1_nonclustered
CREATE UNIQUE CLUSTERED INDEX [order_line_i1_clustered] ON [dbo].[order_line]
(
[ol_w_id] ASC,
[ol_d_id] ASC,
[ol_o_id] ASC,
[ol_number] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = OFF, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
order_line_i1_clustered
的大小為18MB~19MB。exec sp_spaceused order_line
命令檢視。我們也可以通過另外一種方式來證明,通過查詢索引ID,再使用dbcc ind將索引的所有頁返回,然後再計算索引頁的結果
SELECT t.name AS TableName,i.name AS IndexName,i.index_id,i.type_desc
FROM sys.dm_db_partition_stats AS s
INNER JOIN sys.indexes AS i
ON s.object_id = i.object_id
AND s.index_id = i.index_id
INNER JOIN sys.tables AS t
ON t.object_id = i.object_id
WHERE t.name='order_line'
CREATE TABLE dbcc_ind_result (
PageFID int,
PagePID int,
IAMFID int,
IAMPID int,
ObjectID int,
IndexID int,
PartitionNumber int,
PartitionID bigint,
iam_chain_type varchar(30),
PageType int,
IndexLevel int,
NextPageFID int,
NextPagePID int,
PrevPageFID int,
PrevPagePID int
);
GO
INSERT INTO dbcc_ind_result exec('DBCC IND(0,order_line,1)');
GO
INSERT INTO dbcc_ind_result exec('DBCC IND(0,order_line,5)');
GO
SELECT d.IndexID,i.name,COUNT(*) AS PageCount,COUNT(*)*8 AS SizeKB
FROM dbcc_ind_result d
INNER JOIN sys.indexes AS i
ON d.ObjectID = i.object_id
AND d.IndexID = i.index_id
WHERE d.PageType=2
GROUP BY d.IndexID,i.name
GO
前文提到聚集索引的葉節點存放的是資料頁,而非聚集索引葉節點存放的是指標來指向資料的位置,資料的位置可以是堆(head)的RID,也可以時聚集索引的葉節點。下面建立一張測試表來驗證。
DROP TABLE IF EXISTS dbo.Test1;
CREATE TABLE dbo.Test1 (
C1 INT,
C2 INT);
WITH Nums
AS (SELECT TOP (10000)
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS n
FROM master.sys.all_columns AS ac1
CROSS JOIN master.sys.all_columns AS ac2)
INSERT INTO dbo.Test1 (
C1,
C2)
SELECT n,
2
FROM Nums;
SET STATISTICS TIME;
SET STATISTICS IO;
SELECT t.C1,t.C2
FROM dbo.Test1 AS t
WHERE C1 = 1000;
執行後可以看到統計資訊項,發生了22個邏輯讀:
在C1列建立1個非聚集索引後,再觀察統計資訊和執行計劃是否發生變化
CREATE NONCLUSTERED INDEX incl ON dbo.Test1(C1);
建立非聚集索引的過程中,消耗了和前一個查詢相同的資源,統計資訊一樣:
Bmk1000
,再將該指標資訊到堆中的RID,再返回資料,這個過程在表中只需要讀取1行資料。在非聚集索引的基礎上,我們再建立一個聚集索引,通過語句的執行計劃來了解讀取資料的方式。
CREATE CLUSTERED INDEX icl ON dbo.Test1(C1);
建立聚集索引的過程中,產生的統計資訊要比非聚集要多,消耗資源也要更多:
22
次這條統計資訊,完成了整個聚集索引的建立。24
次這條統計資訊,完成了整個非聚集索引的指標資訊更新。SELECT t.C1,t.C2
FROM dbo.Test1 AS t WITH(INDEX = incl)
WHERE C1 = 1000;
發現這種讀取資料的方式要消耗更多的邏輯讀,比RID多了1次邏輯讀,比聚集索引多了2次邏輯讀:
C1=1000
所在的行,然後再將輸出的指標資訊Uniq1001
到聚集索引中執行鍵值查詢,返回資料。行儲存索引的聚集索引和非聚集索引在生產環境上普遍都會使用到,在本文的基礎上,我們進行簡單總結。
本次僅對索引的基本知識進行介紹,後續再根據不同的使用場景來驗證和說明。