SQL?Server索引結(jié)構(gòu)的具體使用
索引是數(shù)據(jù)庫的基礎(chǔ),只有先搞明白索引的結(jié)構(gòu),才能搞明白索引運行的邏輯
本文通過 索引表、數(shù)據(jù)頁、執(zhí)行計劃、IO統(tǒng)計、B+Tree 來盡可能的介紹 SQL 語句中 WHERE 部分,和 SELECT 部分 的運行邏輯
名詞介紹
B+Tree:一種數(shù)據(jù)結(jié)構(gòu)
- 數(shù)據(jù)頁:數(shù)據(jù)庫保存數(shù)據(jù)的最小單位。(SQL Server一個數(shù)據(jù)頁的大小是 8K,一個表中所有的數(shù)據(jù)都被保存到一個個的數(shù)據(jù)頁中)
- 索引組織表:大白話一張表有聚集索引就是索引組織表(把表中的數(shù)據(jù)頁以 B+Tree 的方式組織起來)
- 索引表:一個索引對應(yīng)一張索引表,索引表中每條數(shù)據(jù)都對應(yīng)一張數(shù)據(jù)頁。
通過DBCC IND(數(shù)據(jù)庫, 表名, 索引Id) 命令可以獲取到表中指定索引的索引表信息
通過DBCC PAGE(數(shù)據(jù)庫, 1, 數(shù)據(jù)頁Id, 3) 命令可以獲取到某個數(shù)據(jù)頁中的數(shù)據(jù)
B+Tree結(jié)構(gòu)
準(zhǔn)備數(shù)據(jù)
DROP TABLE Org_User -- 創(chuàng)建測試表 CREATE TABLE Org_User(Id INT,UserName NVARCHAR(50),Age INT) -- 創(chuàng)建聚集索引和非聚集索引 CREATE CLUSTERED INDEX Org_User_Id ON Org_User(Id) CREATE NONCLUSTERED INDEX Org_User_Name ON Org_User(UserName) CREATE TABLE #Temp(Id INT) INSERT INTO #Temp VALUES(1) INSERT INTO #Temp VALUES(2) INSERT INTO #Temp VALUES(3) INSERT INTO #Temp VALUES(4) INSERT INTO #Temp VALUES(5) INSERT INTO #Temp VALUES(6) INSERT INTO #Temp VALUES(7) INSERT INTO #Temp VALUES(8) INSERT INTO #Temp VALUES(9) INSERT INTO #Temp VALUES(10) -- 批量插入10W條數(shù)據(jù) INSERT INTO dbo.Org_User SELECT T1.Id, 'UserName_' + CONVERT(NVARCHAR(20), T1.Id) AS 'UserName', T1.Id + 10 AS 'Age' FROM ( SELECT TOP 100000 Id = ROW_NUMBER() OVER (ORDER BY T1.Id) FROM #Temp AS T1 CROSS JOIN #Temp AS T2 CROSS JOIN #Temp AS T3 CROSS JOIN #Temp AS T4 CROSS JOIN #Temp AS T5 ORDER BY T1.Id ) AS T1
SELECT name, index_id,type_desc FROM SYS.INDEXES WHERE object_id = OBJECT_ID('Org_User'); SELECT index_id , index_type_desc , index_depth , page_count FROM sys.dm_db_index_physical_stats(DB_ID('Core2022'), OBJECT_ID('Org_User'), NULL, NULL, NULL)
在 sys.dm_db_index_physical_stats 這張系統(tǒng)表中
index_depth 表示索引的深度 (對應(yīng)上圖B+Tree就是樹的高度)
page_cout 表示索引數(shù)據(jù)頁的數(shù)量 (對應(yīng)上圖B+Tree就是葉子節(jié)點的數(shù)量)
這里獲取索引信息主要是為了 index_id
索引表
DBCC IND(Core2022, Org_User, 1)
DROP TABLE dbcc_ind -- 創(chuàng)建一張表用來保存索引表信息 CREATE TABLE dbcc_ind ( PageFID NUMERIC(20), PagePID NUMERIC(20), IAMFID NUMERIC(20), IAMPID NUMERIC(20), ObjectID NUMERIC(20), IndexID NUMERIC(20), PartitionNumber NUMERIC(20), PartitionID NUMERIC(20), iam_chain_type VARCHAR(100), PageType NUMERIC(20), IndexLevel NUMERIC(20), NextPageFID NUMERIC(20), NextPagePID NUMERIC(20), PrevPageFID NUMERIC(20), PrevPagePID NUMERIC(20) ) --DROP PROC proc_dbcc_ind -- 創(chuàng)建存儲過程 CREATE PROC proc_dbcc_ind AS DBCC IND(Core2022,Org_User,1) -- 把索引表中的數(shù)據(jù)批量插入到 dbcc_ind 中 INSERT INTO dbcc_ind EXEC proc_dbcc_ind
SELECT PagePID, -- 改行數(shù)據(jù)對應(yīng)的數(shù)據(jù)頁 IndexLevel, -- 表示改行數(shù)據(jù)的級別 0葉子節(jié)點,1分支節(jié)點,=2根節(jié)點,僅限該Demo NextPagePID, -- 當(dāng)前節(jié)點的后繼節(jié)點 (后面的那個數(shù)據(jù)頁) PrevPagePID -- 當(dāng)前節(jié)點的前驅(qū)節(jié)點 (前面的那個數(shù)據(jù)頁) FROM dbcc_ind
SELECT PagePID, IndexLevel, NextPagePID, PrevPagePID FROM dbcc_ind WHERE IndexLevel = 0 ORDER BY NextPagePID
對 DBCC IND 中的數(shù)據(jù)進(jìn)行一個總結(jié)
通過觀察葉子節(jié)點的數(shù)據(jù)可以得到,每個節(jié)點都有一個前驅(qū)指針和后繼指針,構(gòu)成了一個雙向鏈表
通過 IndexLevel 這個字段區(qū)分 根節(jié)點、分支節(jié)點、葉子節(jié)點
通過 NextPagePID 和 PrevPagePID 兩個字段把相同深度的節(jié)點構(gòu)成了一個雙向鏈表
數(shù)據(jù)頁
DBCC TRACEON(3604) — 打開跟蹤標(biāo)記,不打開的話 DBCC PAGE 只能查看分支節(jié)點中的數(shù)據(jù),不能查看葉子節(jié)點中的數(shù)據(jù)
根節(jié)點
分支節(jié)點
葉子節(jié)點
非聚集索引的葉子節(jié)點
對索引表和根節(jié)點對應(yīng)的數(shù)據(jù)頁,分支節(jié)點對應(yīng)的數(shù)據(jù)頁,葉子節(jié)點對應(yīng)的數(shù)據(jù)頁進(jìn)行總結(jié)
聚集索引
葉子節(jié)點中保存的是 Org_User 表中的數(shù)據(jù)
根節(jié)點和分支節(jié)點中保存的是指向下一級節(jié)點的條件
索引表中同級的節(jié)點都有一個前驅(qū)和后繼指針,這兩個指針把同級的節(jié)點構(gòu)建成了一個雙向鏈表
非聚集索引
根節(jié)點和分支節(jié)點與聚集索引一直,都是指向下一級節(jié)點的條件
葉子節(jié)點有區(qū)別包含 創(chuàng)建非聚集索引是指定的Key、指向該行數(shù)據(jù)實際地址的Key、保證索引唯一的Key
UserName 就是創(chuàng)建索引時指定的,如果創(chuàng)建時指定多個,這里也會有多個
Id 這個是指向這行數(shù)據(jù)真實地址的指針表結(jié)構(gòu)不同這個Key也不一樣
索引組織表:這個Key就是創(chuàng)建聚集索引時指定的 Key
堆表:就值這個行數(shù)據(jù)所在堆表的地址
UNIQUIFIER 如果創(chuàng)建索引時指定該索引時唯一索引,那么這里就不會有這個字段,否則就會有這個字段用來區(qū)分重復(fù)的數(shù)據(jù)
通過索引表,找到 Id = 66666 的這行數(shù)據(jù)所在的數(shù)據(jù)頁
對上圖進(jìn)行解釋
拿著 66666 從根節(jié)點指向的數(shù)據(jù)頁開始找
66666 > 36017 所以就跳轉(zhuǎn)到 491 這個數(shù)據(jù)頁
66511 < 66666 ≤ 66669 所以就跳轉(zhuǎn)到 2755 這個數(shù)據(jù)頁
因為 2755 這個數(shù)據(jù)頁已經(jīng)是葉子節(jié)點了,直接在里面搜索 66666
就找到了這一行數(shù)據(jù)
SET STATISTICS IO ON SELECT * FROM Org_User WHERE Id = 66666
回表
因為這條SQL返回的字段是 Select *
非聚集索引里面沒有 Age 這個字段
因此根據(jù) UserName_66666 從非聚集索引中找到這條數(shù)據(jù)之后,根據(jù) Id 到聚集索引里面在查一次,找到 Age 這個字段
覆蓋索引
Select Id,UserName 非聚集索引里面這兩個字段都有,所以就沒有必要在查詢聚集索引了
舉一個例子
SET STATISTICS IO ON SELECT * FROM [Org_User] WHERE Id >= 1 AND Id <= 10 SELECT * FROM [Org_User] WHERE Id IN (1,2,3,4,5,6,7,8,9,10) -- 上面這兩個SQL只有在 Id 為 Int 類型的時候才等價,在等價的前提下 -- 第一個SQL的效率要遠(yuǎn)超于第二個SQL /* SET STATISTICS IO ON (開啟后輸出的內(nèi)容) (10 行受影響) 表 'Org_User'。掃描計數(shù) 1,邏輯讀取 3 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。 (10 行受影響) 表 'Org_User'。掃描計數(shù) 10,邏輯讀取 30 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。 很明顯 第一個SQL只有3次邏輯讀,而第二個有30次邏輯讀 */
只有搞明白了索引運行的邏輯,結(jié)合執(zhí)行計劃等工具,才能搞明白什么情況下那些SQL更好
謠言:
COUNT(*) 和 COUNT(列) 誰快,誰慢
首先這兩種寫法都不等價 COUNT(*) 是所有的數(shù)據(jù) COUNT(列) NULL值不參與運算,所以如果COUNT的某一列中包含了NULL值算出來的數(shù)據(jù)可能就有問題了
查詢速度
COUNT(*) 更塊
COUNT(列) 會受偏移量和字段中數(shù)據(jù)的大小影響
(通過 SET STATISTICS TIME ON 可以非常簡單的得出結(jié)論)
SQL語句 大表寫前面,小表寫后面
當(dāng)前數(shù)據(jù)庫都會對SQL進(jìn)行優(yōu)化,所以無所謂誰在前,誰在后
IN 與 EXISTS 誰好誰壞
當(dāng)前數(shù)據(jù)庫都會對SQL進(jìn)行優(yōu)化,所以無所謂誰好,誰壞
這些坑人的謠言還有很多,有些在老版本的數(shù)據(jù)庫是對的,在當(dāng)前的數(shù)據(jù)庫中已經(jīng)過時了。
到此這篇關(guān)于SQL Server索引結(jié)構(gòu)的具體使用的文章就介紹到這了,更多相關(guān)SQL Server 索引結(jié)構(gòu)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SQL?Server數(shù)據(jù)庫中已存在名為'student'對象的解決辦法
這篇文章主要給大家介紹了關(guān)于SQL?Server數(shù)據(jù)庫中已存在名為'student'對象的解決辦法,解決方法很簡單,并且也很實用,不止有這一個用處,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11sqlserver數(shù)據(jù)庫導(dǎo)入方法的詳細(xì)圖文教程
導(dǎo)入數(shù)據(jù)也是數(shù)據(jù)庫操作中使用頻繁的功能,下面這篇文章主要給大家介紹了關(guān)于sqlserver數(shù)據(jù)庫導(dǎo)入方法的詳細(xì)圖文教程,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10Sql?Server高版本數(shù)據(jù)庫數(shù)據(jù)備份后還原到低版本數(shù)據(jù)庫詳細(xì)步驟
不同版本SQL?Server數(shù)據(jù)庫備份還原存在問題,不能從高版本的數(shù)據(jù)庫導(dǎo)入到低版本數(shù)據(jù)中,這篇文章主要給大家介紹了關(guān)于Sql?Server高版本數(shù)據(jù)庫數(shù)據(jù)備份后還原到低版本數(shù)據(jù)庫的詳細(xì)步驟,需要的朋友可以參考下2023-10-10sql server 2000中禁止創(chuàng)建表(權(quán)限設(shè)置方法)
最近發(fā)現(xiàn)數(shù)據(jù)庫中發(fā)現(xiàn)了三個臨時表,為了安全這里為大家分享下sql server 2000中禁止創(chuàng)建表的方法,網(wǎng)上都么有的,腳本之家小編原創(chuàng)2015-07-07關(guān)系型數(shù)據(jù)庫與非關(guān)系型數(shù)據(jù)庫簡介
數(shù)據(jù)庫有很多種類型,本文對常用的各大關(guān)系型數(shù)據(jù)庫(例如:Oracol、SQLSer、mysql等)和非關(guān)系型數(shù)據(jù)庫(例如:MongoDB、Cassandra、Hadoop HBase等)的優(yōu)勢和缺點做了詳細(xì)的分類分析介紹說明2021-08-08SQL Server2017使用IP作為服務(wù)器名連接服務(wù)器
本文主要介紹了SQL Server2017使用IP作為服務(wù)器名連接服務(wù)器,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09