Sql Server查詢性能優化之不可小覷的書簽查找

来源:http://www.cnblogs.com/shouce/archive/2016/03/14/5274501.html
-Advertisement-
Play Games

出處:http://www.cnblogs.com/lzrabbit/archive/2012/05/21/2499389.html 小小程式猿SQL Server認知的成長 1.沒畢業或工作沒多久,只知道有資料庫、SQL這麼個東東,渾然分不清SQL和Sql Server Oracle、MySql的


出處:http://www.cnblogs.com/lzrabbit/archive/2012/05/21/2499389.html

小小程式猿SQL Server認知的成長

1.沒畢業或工作沒多久,只知道有資料庫、SQL這麼個東東,渾然分不清SQL和Sql Server Oracle、MySql的關係,通常認為SQL就是SQL Server

2.工作好幾年了,也寫過不少SQL,卻渾然不知道索引為何物,只知道資料庫有索引這麼個東西,分不清聚集索引和非聚集索引,只知道查詢慢了建個索引查詢就快了,到頭來索引也建了不少,查詢也確實快了,偶然問之:汝建之索引為何類型?答曰:。。。

3.終於受到刺激開始奮發圖強,買書,gg查資料終於知道原來索引分為聚集索引和非聚集索引,頓時淚流滿面,嗚呼哀哉,吾終知索引為何物也。

4.再進一步學習之亦知聚集索引為物理索引、非聚集索引為邏輯索引,聚集索引為數據的存儲順序,非聚集索引是邏輯索引既對聚集索引的索引

5.再往後學會了查看執行計劃,通過查詢計劃終於對查詢過程有了大概瞭解,也知道了聚集索引掃描和表掃描沒有用到索引,看到聚集索引、索引查找高興的眉飛色舞,看到RID、鍵查找暗自竊喜,瞧,鍵查找肯定就是關鍵字查找了,用著索引呢,效率肯定高,於是每次寫完sql都要觀看下其執行計劃,表掃描的乾貨統統不要,俺只要索引查找、鍵查找。

6.自信滿滿的過著悠哉的小日子,突然有一天迷茫了,為嘛俺明明在這個欄位上建立了索引,它她妹的老給我顯示聚集索引掃描的,難道查詢優化器發燒了,實際執行下,發現實際的執行計劃還是表掃描,這下徹底迷惑了,興許是查詢優化器顯示的有問題吧。

7.繼續深入學習終發現,資料庫這潭水太深了,瞭解的太片面了,想想從猿到人的進化過程吧,恩恩,現在就是一個靈智初開的程式猿,向著偉大的程式員奮勇前進

恩恩,跑題了,進入我們的主題:資料庫的書簽查找

認識書簽查找

書簽查找這個詞可能對於很多開發人員比較陌生,很多人都遇到過,但是卻沒引起足夠的重視以至於一直都忽略它的存在了

定義:當查詢優化器使用非聚集索引進行查找時,如果所選擇的列或查詢條件中的列只部分包含在使用的非聚集索引和聚集索引中時,就需要一個查找(lookup)來檢索其他欄位來滿足請求。對一個有聚簇索引的表來說是一個鍵查找(key lookup),對一個堆表來說是一個RID查找(RID lookup),這種查找即是——書簽查找(bookmark lookup)。簡單的說就是當你使用的sql查詢條件和select返回的列沒有完全包含在索引列中時就會發生書簽查找。

書簽查找的重要性

1.書簽查找發生條件:只有在使用非聚集索引進行數據查找時才會產生書簽查找,聚集索引查找、聚集索引掃描和表掃描不會發生書簽查找。

2.書簽查找發生頻率:書簽查找發生頻率非常高,甚至可以說大部分查詢都會發生書簽查找,我們知道一個表只能建立一個聚集索引,所以我們的查詢更多的會使用非聚集索引,非聚集索引不可能覆蓋所有的查詢列,所以會經常性產生書簽查找。

3.書簽查找的影響:導致索引失效的主要原因之一。書簽查找根據索引的行定位器從表中讀取數據,除了索引頁面的邏輯讀取外,還需要數據頁面的邏輯讀取,如果查詢的結果返回數據量較大會導致大量的邏輯讀或者索引失效,這也是為什麼我們查看查詢計劃時有時明明在查詢列上建立了索引,查詢優化器卻依然使用表掃描的原因。

4.如何消除書簽查找:
  1.使用聚集索引查找,聚集索引的葉子節點就是數據行本身,因此不存在書簽查找
  2.聚集索引掃描、表掃描,說白了就是啥索引都不建直接全表掃描,肯定不會發生書簽查找,不過效率嗎。。。
  3.使用非聚集索引的鍵列包含所有查詢或返回的列,這個不靠譜,非聚集索引最大鍵列數為16,最大索引鍵大小為900位元組,就算你有勇氣在16列上全部建立索引,那如果表的列數超過16列了你咋辦,還有索引列長度之和不能超過900位元組,所以不可能讓非聚集索引包含所有列,而且索引涉及到得列越多維護索引的開銷也就越大。
  4.使用include,嗯,這是個好東東,索引做到只能包含16列且不能超過900位元組,include不受此限制,最多可以包含1023列怎麼也夠你用了,而且對長度也沒有限制你可以隨心所欲的包含nvarchar(max)這也的列,當然了text之流就不要考慮了
  5.其它,其它還有神馬呢,這個我也不知道了,估計應該、可能、大概木有了吧,若有知道的兄弟可以告訴我聲哈

可能上面說的有點抽象,我們開看看具體的例子

一般我們的資料庫都會建上聚集索引(一般大家喜歡建表時有用沒有肯定先來個自增ID列當主鍵,這個主鍵SQL Server預設就給你創建成聚集索引了),故我們這裡都假設表上已經建立了聚集索引,不考慮堆表(就是沒有聚集索引的表)

1.首先創建表Users、插入一些示例數據並建立聚集索引PK_UserID 非聚集索引IX_UserName

複製代碼

--懶得的肥兔 http://http://www.cnblogs.com/lzrabbit/
--創建表Users
Create table Users
(
    UserID int identity,
    UserName nvarchar(50),
    Age int,
    Gender bit,
    CreateTime datetime
)
--在UserID列創建聚集索引PK_UserID
create unique clustered index PK_UserID on Users(UserID)
--在UserName創建非聚集索引IX_UserName
create index IX_UserName on Users(UserName)

--插入示例數據
insert into Users(UserName,Age,Gender,CreateTime)
select N'Bob',20,1,'2012-5-1'
union all
select N'Jack',23,0,'2012-5-2'
union all
select N'Robert',28,1,'2012-5-3'
union all
select N'Janet',40,0,'2012-5-9'
union all
select N'Michael',22,1,'2012-5-2'
union all
select N'Laura',16,1,'2012-5-1'
union all
select N'Anne',36,1,'2012-5-7'
複製代碼

 

2.執行以下查詢並查看查詢計劃,可以看到第一個SQL執行聚集索引掃描,第二個SQL執行聚集索引查找都沒有使用到書簽查找


--懶得的肥兔 http://http://www.cnblogs.com/lzrabbit/
select * from Users
select * from Users where UserID=4

3.比較以下幾個查詢SQL,觀察其查詢計劃,思考下為什麼會發生書簽查找

複製代碼
--查詢1:使用索引IX_UserName,選擇列UserID,UserName,查詢條件列為UserName
select UserID,UserName from Users with(index(IX_UserName)) where UserName='Robert'

--查詢2:使用索引IX_UserName,選擇列UserID,UserName,Age,查詢條件列為UserName
select UserID,UserName,Age from Users with(index(IX_UserName)) where UserName='Robert'

--查詢3:使用索引IX_UserName,選擇列UserID,UserName,查詢條件列為UserName,Age
select UserID,UserName from Users with(index(IX_UserName)) where UserName='Robert' and Age=28

--查詢4:使用索引IX_UserName,選擇列所有列,查詢條件列為UserName
select * from Users with(index(IX_UserName)) where UserName='Robert'
複製代碼

分析:

  查詢1:選擇的列UserID是聚集索引PK_UserID的鍵列,UserName為索引IX_UserName的鍵列,查詢條件列為UserName,由於索引IX_UserName包含了查詢用到得所有列,所以僅需要掃描索引即可返回查詢結果,不需要再額外的去數據頁獲取數據,故不會發生書簽查找

  查詢2:選擇列Age不包含在聚集索引PK_UserID和IX_UserName中,故需要進行額外的書簽查找

  查詢3:查詢條件Age列不包含在聚集索引PK_UserID和IX_UserName中,故需要進行額外的書簽查找

  查詢4:包含了所有的列,Age、Gender、CreateTime列均不在聚集索引PK_UserID和IX_UserName中,所以需要書簽查找以定位數據

 這裡解釋下:查詢中用到的列無論是一列還是多列不在索引覆蓋範圍查詢開銷基本上一樣,每條記錄均只需要一次書簽查找開銷,不會說因為查詢3只有一個Age列,查詢4有Age、Gender、CreateTime 3列不在索引覆蓋範圍而導致額外的開銷  

書簽查找是怎麼發生的

和許多人一樣看到大神們畫的二叉樹索引結構圖就腦袋大,看得雲里霧裡,所以這裡我們以表Users為例來說聚集索引(PK_UserID)和非聚集索引(IX_UserName)的結構可以簡單的表示為下圖

首先我們來看聚集索引PK_UserID,對於聚集索引來說數據行就是其葉子節點,故當執行聚集索引查找時找到了具體的鍵值後就可以直接去葉子節點獲取所有需要的數據不需要進行額外的邏輯讀,比如select * from Users where UserID=2,根據值2在索引PK_UserID中找到UserID為2的值後去葉子節點就可以拿到所需數據,然後返回查詢結果

然後看非聚集索引IX_UserName,上面我們說過非聚集索引覆蓋的列為非聚集索引的鍵列+包含的列+聚集索引的鍵列,對於IX_UserName來說就是如圖中所示鍵列UserName保存在索引的二叉樹節點中,聚集索引的列包含在其葉子節點中,這也就形成了對列(UserName,UserID)的覆蓋,對於查詢1(select UserID,UserName from Users with(index(IX_UserName)) where UserName='Robert')來說查詢只用到了UserName,UserID列,這樣只需要掃描索引IX_UserName即可拿到所有數據然後進行結果返回,而對於查詢2、查詢3來說由於需要用到Age列,而索引IX_UserName中並沒有包含Age列,這時就需要個書簽查找(bookmark lookup)根據葉節點中的RowID去定位到具體的數據行獲取Age列值,對於示例查詢來說先根據索引IX_UserName定位Robert所在行,然後根據RowID=3去數據表裡獲取Age值,然後完成查詢,對於查詢4來說需要更多的列(Age,Gender,CreateTime),同樣定位到Robert所在行RowID=3,去數據表一次性拿到Age,Gender,CreateTime數據然後返回,這樣就形成了書簽查找(查詢計劃中顯示為鍵查找或RID查找)

書簽查找的對查詢性能的影響

 

--這是我們現在使用的索引
create index IX_UserName on Users(UserName)

 

打開IO統計並執行下麵兩個查詢

--set statistics io on
select * from Users where UserName like 'ja%'

select * from Users with(index(IX_UserName))  where UserName like 'ja%'

 

 

兩個查詢都返回2條數據,聚集索引掃描僅僅2次邏輯讀,使用索引IX_UserName卻達到了6次的邏輯讀

我們示例的數據量比較小,所以感受不明顯,不過我們卻也看到了我們在UserName列上市建立了索引 IX_UserName,預設情況下查詢優化器並沒有使用我們的索引,而是選擇了表掃描,僅僅需要2次邏輯讀就拿到了我們需要的數據,在我們使用索引提示強制查詢優化器使用索引IX_UserName後,同樣也是返回2條數據,邏輯讀缺達到了驚人的6次,看查詢計劃使用IX_UserName後發生了書簽查找,而這個開銷主要是有書簽查找造成的,而且隨著我們返回數據量的增加,由書簽查找導致的邏輯讀將會成直線上升,造成的結果就是查詢開銷比進行全表掃描還要大的多,最終導致索引失效

使用覆蓋索引避免書簽查找

覆蓋索引是指非聚集索引上的列(鍵列+包含列) + 聚集索引的鍵列包含了查詢中用到的所有列,對於索引IX_UserName來說索引覆蓋列就是(UserName,UserID)。若查詢中只用到了索引所覆蓋的列,那麼只需掃描索引即可完成查詢,若用到了索引覆蓋範圍以外的列就需要書簽查找來獲取數據,當這種查找發生次較多時就會導致索引失效從而導致表掃描,因為查詢優化器是基於開銷的優化器,當其發現使用非聚集索引引發的書簽查找開銷比表掃描開銷還大時就會放棄使用索引,轉向表掃描。

1.在UserName,Age列上重建索引IX_UserName,這時對於索引IX_UserName來說覆蓋列變為(UserName,Age,UserID),再次執行上面的查詢SQL可以發現查詢計劃已經發生變化


--懶得的肥兔 http://http://www.cnblogs.com/lzrabbit/
drop index IX_UserName on Users
create index IX_UserName on Users(UserName,Age)

我們可以看到查詢2、查詢3的書簽查找已經消失,因為索引IX_UserName包含了查詢中用到得所有列(UserID,UserName,Age),查詢4因為選擇返回所有列我們的索引沒有包含Gender和CreateTime列,故還是會進行書簽查找

這時索引IX_UserName結構表示如下

  
可見對於查詢2、查詢3僅僅通過索引IX_UserName既可以拿到需要的列UserName,Age,UserID,而對於查詢4索引並沒有全部覆蓋還是需要進行書簽查找

2.繼續修改我們的索引IX_UserName,使用include包含非鍵列(鍵列就是索引上的列,非鍵列就是索引之外的列,對於include來說就是存放於非聚集索引葉子節點上的列,聚集索引的列也放在非聚集索引的葉子節點上)

--懶得的肥兔 http://http://www.cnblogs.com/lzrabbit/
drop index IX_UserName on Users create index IX_UserName on Users(UserName,Age) include(Gender,CreateTime)

可以看到我們修改索引使用include包含了Gender,CreateTime後,索引IX_UserName達到了對數據表Users的所有列的全覆蓋,這時候毫無疑問的查詢2、查詢3沒有出現書簽查找,查詢4的書簽查找也消失了。

此時索引IX_UserName 結構如下

索引IX_UserName已經達到了對Users表的全覆蓋,對於我們的查詢2、查詢3、查詢4來說,僅通過索引IX_UserName即可完成查詢,不需要進行書簽查找。

這時我們再來看一下這兩個查詢的開銷及查詢計劃,可以看到不需要我們進行索引提示,查詢優化器已經自動選擇了我們的索引,邏輯讀也降至了2次

select * from Users where UserName like 'ja%'

select * from Users with(index(IX_UserName))  where UserName like 'ja%'

關於Include請參考 SQL Server 索引中include的魅力(具有包含性列的索引)

  這裡說明下書簽查找對查詢性能有著較大的影響並且基本上不可避免,這並不意味著書簽查找就是洪水猛獸,原來我們不是也不知道啥叫書簽查找麽,查詢性能一樣也不差,是吧,呵呵。書簽查找也說明瞭為什麼我們不推薦寫sql時使用select *,也解釋了為什麼有時候我們的索引會失效,同時可以作為優化查詢性能考慮的一個方面,在設計表和索引時儘量規避書簽查找帶來的負面影響,比如非聚集索引儘量選擇高選擇性的列即返回儘量少的行,需要大批量數據查詢時儘量使用聚集索引等。  

  本文中為了便於演示僅僅使用了有幾條數據的表,而且查詢中為了使用索引都用了索引提示,實際開發中請不要使用索引提示,查詢優化器大多數情況下會為我們生成最優(最優不代表開銷最小,只要開銷足夠小即認為最優)的執行計劃,索引結構裡面用到得RowID也僅僅是為了演示虛構出來的,我們只要認為它是對於數據行的一個標識位就行了。

  此文旨在讓我們認識書簽查找並意識到書簽查找的意義,從而對於索引失效原因有清晰的認識,更好的理解查詢計劃。


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

-Advertisement-
Play Games
更多相關文章
  •      圖片拉伸 lxx__lxx__lxx__lxx__lxx__lxx__lxx__lxx__lxx__lxx___ lxx__lxx__lxx__lxx__lxx__lxx__lxx__lxx__lxx__lxx___   1. 如果圖片比較大得話,不要用 [UIImage imageNam
  • 前言:這篇GCD的博文是本人閱讀了很多海內外大神的關於GCD的文章,以及結合之前自己對GCD的粗淺的認識,然後取其精華,去其槽粕,綜合起來的筆記,而且是儘可能的以通熟易懂的並且是正確的理論論述方式呈現給讀者,同時也是講大神博客中有的深澀的理論理解的通熟易懂轉述給讀者,已經是儘可能的讓讀者深入理解和掌
  • 分類:C#、Android、VS2015; 創建日期:2016-03-13 一、簡介 Android提供的Camera有兩個典型的版本,一個是在Android 5.0以前提供的,稱為Camera;另一個是從Android 5.0開始提供的,稱為Camera2。 這裡僅演示使用系統Camera程式實現...
  • 服務 服務沒有界面,一直運行在後臺, 運行在獨立的一個進程裡面 服務沒有界面,一直運行在後臺,預設是運行當前的應用程式進程裡面。 建立一個類繼承Service類 在清單文件中註冊service   只會在開啟服務時初始化一次   每次開啟服務都會執行調用   停止服務時,只執行一次   onCrea
  • 一、設置UITableView裡面的頂部 cell 距離頂部的間距的三種方式: 方法 1. 直接設置: self.tableView.contentInset = UIEdgeInsetsMake(HWStatusCellMargin, 0, 0, 0); @interface HWHomeView
  • 1. ARC裡面預設情況下所有指針都是強指針。 2. 在演示 設置兩個控制器的關係為 父子控制器的關係的時候,將一個控制器B 的view加到 A控制器的view上去的時候,如果A、B不為父子控制器的關係時,旋轉屏幕 的時候 B控制器是不能監聽到屏幕的旋轉的。只有A控制器可以監聽到屏幕旋轉。 3. 屏
  • sql server2000中使用convert來取得datetime數據類型樣式(全) 日期數據格式的處理,兩個示例: CONVERT(varchar(16), 時間一, 20) 結果:2007-02-01 08:02/*時間一般為getdate()函數或數據表裡的欄位*/ CONVERT(var
  • 獲取 資料庫 和 表信息: 確定MySQL支持哪些表類型:mysql> show variables like '%have%';直接得到表類型清單:mysql> show table types;
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...