SQL SERVER大話存儲結構(4)_複合索引與包含索引

来源:http://www.cnblogs.com/xinysu/archive/2017/06/01/6928305.html
-Advertisement-
Play Games

索引這塊從存儲結構來分,有2大類,聚集索引和非聚集索引,而非聚集索引在堆表或者在聚集索引表都會對其 鍵值有所影響,這塊可以詳細查看本系列第二篇文章:SQL SERVER大話存儲結構_(2)_非聚集索引如何查找到行記錄。 非聚集索引內又分為多類:單列索引、複合索引、包含索引、過濾索引等。之前文章有具體 ...


          索引這塊從存儲結構來分,有2大類,聚集索引和非聚集索引,而非聚集索引在堆表或者在聚集索引表都會對其 鍵值有所影響,這塊可以詳細查看本系列第二篇文章:SQL SERVER大話存儲結構_(2)_非聚集索引如何查找到行記錄。     非聚集索引內又分為多類:單列索引、複合索引、包含索引、過濾索引等。之前文章有具體分析過非聚集索引的存儲情況,但是沒有對複合索引及包含索引做過多說明,本文來講講這兩個索引。   

      如果轉載,請註明博文來源: www.cnblogs.com/xinysu/   ,版權歸 博客園 蘇家小蘿蔔 所有。望各位支持!   

1 語法及說明

--複合索引
CREATE INDEX IndexName ON tbname(columna,columnb [,columnc...] )
 
--包含索引
CREATE INDEX IndexName ON tbname(columna [,columnb,columnc...] ) INCLUDE (column1 [,column2,column3...])
     複合索引,顧名思義,及多個列組成的索引,列的順序非常重要,關係到查詢性能,這點後面會說明。      包含索引,建索引SQL 中含有 include 欄位,索引鍵值用於WHERE條件過濾,INCLUDE欄位用於 SELECT 展示,這點後面也會說明。      無論是符合索引還是包含索引,都有索引鍵值長度不能超過900位元組的限制,但是要註意一點,包含索引的include欄位是不包括在裡邊的。

2 索引頁存儲情況

    從索引頁的存儲情況來分析,分析過程中,重點在查看複合索引跟包含包含索引在 子節點及葉子結點的鍵值情況。

2.1 創建測試表格

    創建表格 tbindex,建立兩個測試索引,同時造數據。
 1 CREATE TABLE tbindex(
 2 id int identity(1,1) not null primary key ,
 3 name varchar(50) not null,
 4 type varchar(10) not null,
 5 numbers int not null
 6 )
 7 GO
 8  
 9 CREATE INDEX ix_number_name ON tbindex(numbers,name)
10 GO
11 CREATE INDEX ix_name ON tbindex(numbers) INCLUDE (name)
12 GO
13  
14 DECLARE @ID INT
15 SET @ID=1
16 WHILE @ID<=5
17 BEGIN
18      INSERT INTO tbindex(name,type,numbers)
19      SELECT
20            name,
21          type,
22          object_id+@id
23      FROM sys.objects
24  
25         SET @ID=@ID+1
26 END

2.2 分析索引行

--查看該表格索引的id情況
SELECT * FROM sys.indexes WHERE object_id=object_id('tbindex')
--PK__tbindex__3213E83F89582AC3    1
--ix_number_name    2
--ix_number    3
 
DBCC traceon(3604)
DBCC ind('dbpage','tbindex',-1)
 
DBCC PAGE('dbpage',1,395,3)
DBCC PAGE('dbpage',1,396,3)
 
DBCC PAGE('dbpage',1,397,3)
DBCC PAGE('dbpage',1,398,3)
 

    分析查看,得知:
  • 複合索引 IX_number_name的索引節點為pageid=395,再挑選一個葉子結點來分析 pageid=396;
  • 包含索引 IX_number 的索引節點為 pageid=397,再挑選一個葉子節點來分析 pageid=398。
  --複合索引,395為索引頁節點,396為索引頁葉子節點 DBCC PAGE('dbpage',1,395,3) DBCC PAGE('dbpage',1,396,3)   --包含索引,397為索引頁節點,398為索引頁葉子節點 DBCC PAGE('dbpage',1,397,3) DBCC PAGE('dbpage',1,398,3)    

 

    從這裡可以看出,複合索引跟包含索引的 所有索引列都會存儲在索引葉子節點跟子節點,但是包含索引 的INCLUDE列,不在索引頁的子節點存儲,僅存儲在 索引頁的葉子節點上。     從這裡不難理解,為什麼之前說 include列用於 select 列,而不用於 where 列過濾。因為非聚集索引當索引頁面有多層的時候,是先查詢 索引的子節點,再查詢索引的葉子節點,而包含索引的INCLUDE列不在葉子節點中存儲,無法根據其來進行過濾。

3  對查詢的影響

3.1 複合索引查詢註意事項

     由於需要數據量作為實驗支持,所以不用之前分析索引行結構的表格tbindex,換個高大上 tb_composite 如下。  
 1 create table tb_composite(
 2 id int identity(1,1) not null primary key,
 3 name varchar(50) not null,
 4 userid int not null,
 5 timepoint datetime not null
 6 )
 7 GO
 8  
 9 create index ix_userid_name on tb_composite(userid,name)
10 GO
11  
12 create index ix_userid on tb_composite(userid)
13 GO
14  
15 INSERT INTO tb_composite(name,userid,timepoint)
16 SELECT
17       newid(),orderid%10000 ,CreatedDate
18 FROM ORDERS
大數據表格     至此,測試表格建立完成,開始分析索引頁面信息,統計表格tb_composite信息如下:  
 1 --查看表格的數據大小跟非聚集索引大小
 2 WITH DATA AS (
 3 SELECT
 4  
 5       O.name tb_name,
 6       reservedpages = SUM (reserved_page_count),
 7       usedpages = SUM (used_page_count),
 8       pages = SUM (CASE WHEN (index_id < 2) THEN (in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count) ELSE 0 END ),
 9       rowCounts = SUM (CASE WHEN (index_id < 2) THEN row_count ELSE 0 END )
10 FROM sys.dm_db_partition_stats S
11 JOIN sys.objects o on s.object_id=o.object_id
12 WHERE O.type='U'
13 GROUP BY O.name
14 )
15 SELECT
16  
17          tb_name,
18          rowCounts,
19          reservedpages*8/1024 reserved_Mb,
20          pages*8/1024 data_Mb,
21          index_Mb=(usedpages-pages)*8/1024,
22          unused_Mb=case when usedpages>reservedpages then 0 else (reservedpages-usedpages)*8/1024 end
23 FROM DATA
24 WHERE tb_name = 'tb_composite'
25 ORDER BY reserved_Mb DESC
26 Go

--詳細分析每一個索引的索引頁面數量 create table tbind(PageFID int,   PagePID int,IAMFID int,IAMPID int,ObjectID int,IndexID int,PartitionNumber int,PartitionID varchar(50),iam_chain_type varchar(50) ,PageType int,IndexLevel int,NextPageFID int,NextPagePID int,PrevPageFID int,PrevPagePID int )   INSERT INTO TBIND EXEC ('DBCC IND(''yaochufa'',''tb_composite'',-1) ')   SELECT         i.name,i.index_id,p.page_nums FROM sys.indexes i join (SELECT IndexID,count(*) page_nums FROM tbind group by IndexID ) p on i.index_id=p.IndexID WHERE object_id=object_id('tb_composite') ORDER BY index_id  
    可以看到這個表格的非聚集索引總大小 ≈  598Mb ≈  (43022+33279)*8k/1024 ≈  596Mb 。     ix_userid_name 明顯要比 ix_userid 存儲的頁面多,這是因為 ix_userid_name 比 ix_userid 多存儲了 name 這個索引鍵值,索引頁的增加,意味著使用這個索引就會相應增加 IO 。     比如一下兩個SQL: SET STATISTICS IO ON --執行前,按下快捷鍵:Ctrl+M, 執行SQL後會顯示實際執行的執行計劃 (註意,Ctrl+L,則為 預估的執行計劃)   SELECT * FROM tb_composite WITH(INDEX=ix_userid_name) WHERE userid =6500 SELECT * FROM tb_composite WITH(INDEX=ix_userid) WHERE userid =6500       查看其IO情況:            走複合索引會比單列索引要多出3個IO,userid 條件的擴大這個IO差別也會逐步加大。          查看執行計劃如下:          可以看出,兩者都是先根據索引 進行 index seek 查找到相應的索引行,再根據索引行上的 主鍵,去聚集索引中進行 key lookup查找行記錄。兩者的執行計劃是一模一樣的。這裡加多一個SQL查詢。   SELECT * FROM tb_composite WHERE name='6CDC4A13-36FF-4FA2-94D0-F1CBEA40852C'          name這一列,不存在單列索引,存在於複合索引 ix_userid_name(userid,timepoint,name) 中,那麼 這個查詢能否根據 這個索引進行查找呢?     答案是:NO NO NO ,資料庫會根據其IO情況來做選擇,有兩種可能,一種是根據主鍵做全表scan,另外一種是 對 複合索引 進行 index scan 全掃描,然後再根據鍵值去 聚集索引上查找相應的 行記錄。     且看執行計劃跟IO如下,可以看出,邏輯讀基本上把所有數據頁(聚集索引葉子節點)都掃描出來,一次IO是一個8kb的data page。       來吧,總結一下:
  1. 最左匹配原則:複合索引 鍵值列假設為(a, b, c, d, e),則等同於索引這幾個索引:(a)、(a, b)、(a, b, c)、(a, b, c, d)、(a, b, c, d, e)
    1. 當where條件 符合 最左匹配原則,那麼,執行計劃則是 INDEX SEEK ,走索引查找;
    2. 當where條件 不符合 最左匹配原則,則根據性能評估,走primary index scan 或者 非聚集索引掃描再根據鍵值去 primary key lookup ;
  2. 根據最左匹配原則,可以在日常管理中,避免添加一些冗餘冗餘索引
  3. 但是也有一個註意事項:隨著複合索引的列增加,索引頁也會增加,使用其索引會增加一定量的IO,所以,再判斷冗餘索引的時候,需要考慮下這種情況,通常很少碰到這種情形。

3.2 複合索引與包含索引的查詢區別

    前面測試已經瞭解 複合索引 跟 包含索引 的 存儲結構,這裡進行查詢測試。這裡註意 索引頁數量 = 索引節點頁+索引葉子節點頁。     先創建 包含索引表格,造數據。 CREATE TABLE tb_include( id int identity(1,1) not null primary key, name varchar(50) not null, userid int not null, timepoint datetime not null ) GO   CREATE INDEX ix_userid on tb_include(userid) INCLUDE (timepoint,name) GO   INSERT INTO tb_include( name , userid , timepoint ) SELECT name,userid,timepoint FROM tb_composite GO       做兩個查詢如下: SELECT USERID,name FROM tb_composite  where USERID=71 SELECT USERID,name FROM tb_include  where USERID=71   SELECT USERID,name FROM tb_composite  where USERID=71 AND NAME='010CC1BD-1736-46A8-9497-7F4DBFD082B2' SELECT USERID,name FROM tb_include  where USERID=71 AND NAME='010CC1BD-1736-46A8-9497-7F4DBFD082B2'  

    總結:
  1. 如果where 條件包含include列
    1. include列無法參與 index seek,因為其索引子節點不存在,只存在於索引葉子節點,所以include列一般都是 展示列;
    2. include列由於無法做 where 過濾的 index seed,同比 複合索引,IO相對會較大
  2. 如果展示列僅限於索引鍵值及include列
    1. 包含索引中,根據索引鍵值找到 索引葉子節點後,無鬚根據主鍵值或者RID值 回表 去查詢行記錄,而是直接把 索引葉子節點的 include 列的內容展示即可,減少 回表 的IO;
  3. 如果where條件僅含鍵值列,select 展示列僅含 鍵值列級include列
    1. 兩者性能基本一致,包含索引相對少IO,但是區別不大。
  4. 所有非聚集索引的限制長度是900個位元組,但是 包含索引中的 include列是不計算在索引長度中的,所以如果要是遇到這種索引超過 900 bytes的特殊情況,可以考慮把相關欄位放到include中來處理。

 

 

 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SELECT * FROM (SELECT a.*,rownum row_num FROM (SELECT etpcode,etpename FROM tetp c ORDER BY c.etpcode DESC ) a ) b WHERE b.row_num BETWEEN 1 AND 2 ...
  • 最近在編寫一個小型基於的jsp系統開發。掌握資料庫一直感覺還不錯。但是今天就出現了一個問題困擾我大半天。後來本來準備睡覺,但是覺得今天不解決這個問題恐怕晚上是“徹夜難眠啊”!!於是打開電腦,又開始搗騰。遇到問題首先去網上search了一下。但是大多數的結果只能說:只能遠看而不能解決我的問題。 首先我 ...
  • ORACLE體繫結構包括:實例(Instance),資料庫文件,用戶進程(User process),伺服器進程以及其他文件。 1.ORACLE實例(instance) 1).要訪問資料庫必須先啟動實例,實例啟動時先分配記憶體區,然後再啟動後臺進程,後他進程執行資料庫的輸入,輸出以及監控其他進程。 在 ...
  • 今天在虛擬機上掛載光碟時提示: [root@primary dev]# mount /dev/cdrom /mnt/cdrom mount: you must specify the filesystem type 處理方法: 虛擬機-〉setting->cd/dvd-〉device status- ...
  • 獲得資料庫和表的信息 一般正常的程式員或DBA都會在敲代碼的時候突然想到這樣的一系列問題:我是誰?我在哪?我在乾什麼? 我的資料庫呢?我的表呢?我表怎麼創建的?我該怎麼辦呢?你可能會想到SHOW DATABASES; 命令。But, 這個命令是列出由mysql管理的databases. 不是知道我再 ...
  • 溫習《高性能MySQL》的第一章 MySQL架構與歷史 1.1 MySQL邏輯架構 參考http://www.cnblogs.com/baochuan/archive/2012/03/15/2397536.html 圖1-1:MySQL伺服器邏輯架構圖 最上層的服務並不是MySQL所獨有的,大多數基 ...
  • 提取資料庫所有表的表名、欄位名 在SQLserver 2000中測試 在SQLserver 2005中測試 提取某個表的欄位名 ...
  • 在博客園看到一篇討論特別多的文章“探討SQL Server併發處理存在就更新七種解決方案”,這種業務需求很常見:如果記錄存在就更新,不存在就插入。 最常見的做法: 一個很明顯的問題,在高併發下可能存在操作同一條記錄的多個線程都進入到INSERT環節,導致插入失敗。 上面問題原因在於進入INSERT或 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...