SQL Server索引進階第十三篇:Insert,Update,Delete語句
索引設計是資料庫設計中比較重要的一個環節,對資料庫的效能其中至關重要的作用,但是索引的設計卻又不是那麼容易的事情,效能也不是那麼輕易就獲取到的,很多的技術人員因為不恰當的建立索引,最後使得其效果適得其反,可以說“成也索引,敗也索引”。
本系列文章來自Stairway to SQL Server Indexes ,然後經過我們團隊的理解和整理髮布在agilesharp,希望對廣大的技術朋友在如何使用索引上有所幫助。從本系列文章的第十篇到第十二篇講述了索引的內部結構以及索引結構改變所帶來的影響。在本篇文章中,我們繼續來看由INSERT,DELETE,UPDATE以及MERGE操作對索引資料所產生的影響。首先讓我們分別來看上面每個單獨語句所產生的影響,再談對於上面幾種操作都產生影響的主題:行資料修改與索引資料修改。
INSERT
在第十一篇關於索引碎片的文章中,我們已經介紹了插入語句帶來的影響,這裡只是做一個總結性介紹,更詳細的內容請翻回第十一篇。
無論是將資料插入堆表或是聚集索引表,表上的每一個索引都需要對應新增一個條目,當然過濾索引可能除外。插入時,SQL Server根據索引鍵從跟節點一路向下找到葉子節點,找到葉子節點之後,SQL Server首先檢視頁內空間是否足夠,如果頁內空間足夠,SQL Server就會將資料插入到頁中。
當然了,SQL Server也有可能遇到頁已滿的情況,這時,SQL Server會從分配結構找找到一個空閒頁,接下來的操作取決於所插入資料的索引鍵的順序,根據這個順序,SQL Server會做下面三種操作中的一種:
- 當隨機順序
- 當升序排序:當SQL Server發現新插入的資料按順序應該儲存在當前滿頁的最後,則SQL Server會將這個條目插入到新頁中,注意,僅僅是這一個條目。接下來插入的資料如果還是按照索引的順序插入,則繼續上面的步驟。因此幾乎不存在頁分裂的情況,因此內部碎片會保持在最小。
- 當降序排序:與上面的情況相反,當SQL Server發現新插入的資料應該存在頁的第一個位置時,SQL Server就認為索引是降序排序的,也僅僅只將這一條資料插入到新頁中。內部碎片也幾乎不存在。
將資料插入進頁後,還需要做一些額外工作,比如說指向邏輯相鄰頁的前後連結串列指標需要更新,並且頁分裂後還需要將一行資料提到父節點,也就是非葉子節點,非葉子節點也滿時,最終導致非葉子節點的頁分裂。
DELETE
當從表中刪除一行後,表上的索引中相關條目也需要被刪除。和INSERT一樣,對於每一個索引,SQL Server都會從跟節點向下直到找到葉子節點。當找到葉子節點之後,SQL Server可能會馬上刪除這條資料,也可能不馬上刪除,而是在頁中的標誌位設定這頁已經被刪除。這種邏輯上被刪除但物理上還存在術語稱為:虛影記錄(GHOST Record),在接下來合適的時機,SQL Server才會刪除虛影記錄,我將在本篇文章後面進行詳細闡述。
當表中的資料被標為虛影記錄時,這條記錄就會被接下來的任何查詢所無視。雖然邏輯上這條記錄已經被刪除,但物理上依然存在。虛影記錄的數量可以在sys.dm_db_index_physical_stats這個DMV中進行檢視。
虛影記錄是由於效能和併發的原因被引入,這不僅提高了DELETE語句的效能,如果DELETE被回滾(Rollback),也同樣會提升效能。當回滾資料時,虛影記錄僅僅需要將標誌位改回來,而不是重新根據日誌再建立一條記錄。
虛影記錄何時被刪除取決於多個因素:其中很多因素已經超出了本系列文章的討論範圍。正式因為因素眾多,所以很難知道SQL Server何時真正的刪除虛影記錄。下面是一些影響虛影記錄的因素:
- 如果存在行級鎖,則被刪除的索引條目會被標記為虛影記錄
- 如果存在5000行以上的行鎖,往往會被升級為表鎖
- 使用行版本這種樂觀併發控制也會造成虛影記錄
- 事務完成之前虛影記錄不會被刪除
- SQL Server通過ghost-cleanup執行緒來刪除虛影記錄。但刪除的時機卻無法預料,DELETE操作不會影響ghost-cleanup的行為,但會將新的虛影記錄加到待刪除的虛影記錄佇列末尾,而這個執行緒定期清理這些記錄
- ghost-cleanup執行緒大概每5秒被喚醒一次,每次喚醒大概清理10頁虛影記錄,這個值會隨著SQL Server版本的不同而不同
- 你可以通過呼叫_clean_db_free_space 或 sp_clean_db_file _free_space來強制刪除虛影記錄
一個虛影記錄的例子
為了更好的理解虛影記錄,我們使用一個有20000行記錄的非聚集索引,使得資料填充滿行,我們使用事務刪除大約一半的資料但不Commit.然後通過Sys.dm_index_physical_stats來觀察索引的使用情況,可以看到一部分資料被實際刪除,而部分資料變為虛影記錄。再然後我們提交這個事務,過一段時間就可以看到虛影記錄被刪除。
下面做兩個實驗,第一種是每頁刪除一半的行,另一種是刪除索引前一半的頁。
每次刪除完資料時候,我們使用下面的檢視來看虛影記錄。只有最右邊的列可以看到虛影記錄的資料,檢視程式碼如下。
- USE AdventureWorks;
- GO
- IF EXISTS (SELECT *
- FROM sys.objects
- WHERE name = 'viewTestIndexInfo' and type = 'V')
- BEGIN
- DROP VIEW dbo.viewTestIndexInfo
- END
- GO
- CREATE VIEW dbo.viewTestIndexInfo
- AS
- SELECT IX.name as 'Name'
- , PS.index_level as 'Level'
- , PS.page_count as 'Pages'
- , PS.avg_page_space_used_in_percent as 'Page Fullness (%)'
- , PS.ghost_record_count as 'Ghost Records'
- FROM sys.dm_db_index_physical_stats( db_id(), object_id('dbo.FragTest')
- , default, default
- , 'DETAILED') PS
- JOIN sys.indexes IX
- ON IX.object_id = PS.object_id AND IX.index_id = PS.index_id
- WHERE IX.name = 'PK_FragTest_PKCol';
- GO
程式碼2用於建立測試表並載入資料,我們按照索引的順序載入20000條資料。
(譯者注,這段程式碼作者搞錯了,把程式碼1複製了一遍,我根據自己對文章的理解寫了下面的建立表和載入測試資料的程式碼,由於程式碼2是我根據上下文意思寫的,後面的截圖可能和作者截圖中內容有偏差,所以我按照自己的截圖結果來)
- CREATE TABLE dbo.FragTest
- (PKCol int)
- DECLARE @index INT
- SET @index=1
- WHILE(@index <=20000)
- BEGIN
- INSERT INTO dbo.FragTest(pkcol) VALUES(@index)
- SET @[email protected]+1
- END
- CREATE CLUSTERED INDEX PK_FragTest_PKCol ON dbo.FragTest(pkcol)
測試資料載入完執行,執行SELECT * FROM dbo.viewTestIndexInfo就可以看到如圖1所示的結果。
1301.jpg(15.06 K)
9/9/2012 11:21:20 AM
圖1.幾乎頁滿的索引
執行程式碼3所示的程式碼,從事務中進行隔行刪除。
- BEGIN TRANSACTION
- DELETE DBO.FragTest
- WHERE PKCol % 2 = 0;
- SELECT *
- FROM dbo.viewTestIndexInfo;
- GO
結果如圖2所示。
1302.jpg(13.14 K)
9/9/2012 11:21:20 AM
圖2.索引中包含了虛影記錄
當DELETE語句開始執行時,行鎖會加在6228條記錄上,當這些記錄被刪除後,生成6228條虛影記錄,由於存在的行鎖過多,鎖會升級成表鎖。這時實際上已經刪除了10000-6228=3372條資料,這3372條資料的缺失造成頁面使用百分比從大於97%降低到79%左右。
接下來我們Commit上面未完成的事務,此時後臺的ghost cleanup執行緒就會清除虛影記錄,過幾秒後,我們可以看到如圖3所示,虛影記錄被刪除,頁的使用百分比降低到大約48.6%左右。
1303.jpg(13.19 K)
9/9/2012 11:21:20 AM
圖3.事務提交後頁面使用的百分比
另一個版本虛影記錄的例子
當虛影記錄從葉節點刪除後,可能造成頁中不存在任何資料。此時這個頁就可能被釋放。下面我們來看這種情況。
對於這個例子,我們從新執行程式碼2中建立和載入測試資料的例子。
現在開始刪除索引中前半部分資料,如程式碼4所示。
- BEGIN TRANSACTION
- DELETE DBO.FragTest
- WHERE PKCol <= 20000 / 2;
- SELECT *
- FROM dbo.viewTestIndexInfo;
- GO
此時得到結果如圖4所示。
1304.jpg(14.88 K)
9/9/2012 11:21:20 AM
圖4.刪除前半部分資料後的結果
同樣,大約6000條資料變為虛影記錄,另外10000-6220=3780條資料被實際刪除。由於被刪除的記錄是在物理上連續的,所以一些空頁被釋放,圖4的頁數由之前的33降低到27.
接著,我們COMMIT事務,然後再來看頁數,如圖5所示。
1305.jpg(15.07 K)
9/9/2012 11:21:20 AM
圖5.提交事務後頁數佔用由27降低到18
前一半記錄從索引中刪除之後,沒有記錄的頁被釋放並不再屬於索引,其中在索引中間位置的一頁,其中只刪除了一半左右的行,因此繼續存在,其它頁中頁滿程度保持不變。
那為什麼還存在一條虛影記錄呢?因為第一個葉子節點的地址,和索引的根節點一樣,都是存在系統的metadata中,因此一旦分配了,葉子的第一個節點和最後一個節點永遠不會釋放。ghost-cleanup執行緒會在第一個頁中留下虛影記錄以保證這個頁不會被刪除。
對於非葉子節點來說,刪除意味著直接刪除而不會留下虛影記錄。如果一個非葉子節點的頁中不存在資料,則會被釋放,並刪除其父節點對其的指標。
根節點不屬於這個模式應用之列,根節點永遠不會被刪除。即使索引中不存在任何資料,根節點還是不會被刪除,對於每個索引來說都至少需要存在一頁,這一頁就是根節點。
UPDATE
當表中的資料更新時,索引條目就需要被修改。SQL Server修改資料分為兩種方式,一種是直接UPDATE,另一種是先DELETE再INSERT,SQL Server通常情況下會盡量直接UPDATE,但在特定情況下無法直接UPDATE的時候,SQL Server只能先DELETE再INSERT,這幾種情況如下:
- 更新需要修改索引鍵列的值,需要這一列在索引中重新定位
- 更新可變列的值導致頁無法容納這個更新
- 表上存在DML觸發器
另外,如果被修改的資料包括索引鍵,則所有對應的非聚集索引的書籤值也需要改變。
如果修改的資料不包括索引鍵,則行在索引的位置不會被改變,但條目的大小可能會被改變,如果當前的頁無法容納下新的行,則先DELETE後UPDATE。
MERGE
MERGE操作在SQL Server 2008之後被引入,功能強大、靈活、有用。但實際上,MERGE背後是生成與之等效的INSERT,UPDATE,DELETE語句。使用MERGE語句所帶來的影響和其生成的這三個DML語句所帶來的影響效果相同。
一次性更新索引
當INSERT,UPDATE,DELETE語句執行在單一行時,SQL Server直接執行這個操作並修改與之對應的非聚集索引,但如果是一次性操作在多行時,SQL Server將會有兩個選擇:
一行一行的更新,每更新一行則修改對應的索引
或
一行一行的更新,但不直接修改對應的索引,而是將這個修改列表掛起快取,當所有的行更新完畢後,再根據快取佇列修改索引。
上面第二種方式就是所謂的一次性更新索引(index-at-a-time update)。對於INSERT,UPDATE,DELETE語句來說都可能應用到這種方式。
SQL Server查詢優化器來決定使用那種方式進行更新,一次性更新的行越多,則第二種方式被使用的概率越大。
為了演示這點,我們建立如下程式碼,如程式碼5所示。
- USE AdventureWorks;
- GO
- IF EXISTS (SELECT *
- FROM sys.objects
- WHERE name = 'FragTestII' and type = 'U')
- BEGIN
- DROP TABLE dbo.FragTestII;
- END
- GO
- CREATE TABLE dbo.FragTestII
- (
- PKCol int not null
- , InfoCol nchar(64) not null
- , CONSTRAINT PK_FragTestII_PKCol primary key nonclustered (PKCol)
- );
- GO
- CREATE INDEX IX_FragTestII_InfoCol
- ON dbo.FragTestII (InfoCol);
- GO
然後插入一條資料,如程式碼6所示。
- INSERT dbo.FragTestII
- VALUES (100000, 'XXXX');
下面來看圖6的執行計劃,可以看出只有一個插入,因為涉及的資料量非常少。
1306.jpg(6.19 K)
9/9/2012 11:21:20 AM
圖6.單一表插入的執行計劃
但是一次性批量插入資料情況就不一樣了,這次我們通過程式碼7批量插入資料
- INSERT dbo.FragTestII
- SELECT PKCol, InfoCol
- FROM dbo.FragTest;
下面來看圖7這個查詢計劃,包含了多個操作。主要是將帶插入資料排序後插入索引。
1307.jpg(40.41 K)
9/9/2012 11:21:20 AM
圖7.批量插入
索然這個執行計劃看上去很複雜,將掛起的修改和更新進行排序再插入索引,但這種方法效率更高,因為連續的索引條目被直接插入索引。這種方式使得索引的索引碎片更少。
總結
將一條資料插入的索引根據插入資料的鍵值可能導致三種碎片方式中的一種。
從索引中刪除條目,包括聚集索引中刪除條目,有可能不直接刪除條目,取而代之標記為虛影記錄。虛影記錄只會在葉子節點中存在,SQL Server會在一段時間後刪除虛影記錄,但必須在事務完成之後。
更新索引條目可能直接刪除,也可能刪除後再插入。如果底層表沒有DML觸發器或是更新不會導致索引條目的增加和位置的改變,則UPDATE語句會直接更新。
如果資料修改語句涉及大量的行,SQL Server將會使用一次性更新索引,先更新了表中的資訊,再排序這些更改一次性插入索引。
相關推薦
SQL Server索引進階第十三篇:Insert,Update,Delete語句
索引設計是資料庫設計中比較重要的一個環節,對資料庫的效能其中至關重要的作用,但是索引的設計卻又不是那麼容易的事情,效能也不是那麼輕易就獲取到的,很多的技術人員因為不恰當的建立索引,最後使得其效果適得其反,可以說“成也索引,敗也索引”。 本系列文章來自Stairway to SQL Server I
SQL Server索引進階第十一篇:索引碎片分析與解決(上)
索引設計是資料庫設計中比較重要的一個環節,對資料庫的效能其中至關重要的作用,但是索引的設計卻又不是那麼容易的事情,效能也不是那麼輕易就獲取到的,很多的技術人員因為不恰當的建立索引,最後使得其效果適得其反,可以說“成也索引,敗也索引”。 相關有關索引碎片的問題,大家應該是聽過不少,也許也很多的朋友
SQL Server索引設計 <第五篇>
字段排序 暫停 最快 get include 對象 聚合函數 要花 可能性 SQL Server索引的設計主要考慮因素如下: 檢查WHERE條件和連接條件列; 使用窄索引; 檢查列的選擇性; 檢查列的數據類型; 考慮列順序; 考慮索引
SQL Server索引語法 <第四篇>
相同 alt 不能 之間 cto 事情 col 存儲 過程 從CREATE開始 通過顯式的CREATE INDEX命令 在創建約束時作為隱含的對象 隨約束創建的隱含索引 當向表中添加如下兩種約束之一時,就會創建隱含索引。 主鍵約
SQL之merge into 批量更新資料 Merge關鍵字是一個神奇的DML關鍵字。它在SQL Server 2008被引入,它能將Insert,Update,Delete簡單的併為一句。M
轉載http://www.cnblogs.com/ruiati/archive/2013/01/18/2866017.html Merge關鍵字是一個神奇的DML關鍵字。它在SQL Server 2008被引入,它能將Insert,Up
python進階第1篇 函數入門
避免 活性 保持 分開 append 表達 按順序 lose item 知識內容: 1.函數的作用 2.函數的定義與調用 3.函數的返回值 4.函數的參數 一、函數的作用 1.復用代碼 將可能重復執行的代碼封裝成函數,並在需要執行的地方調用函數,不僅可以實現代碼的復
前端基礎進階(十三):透徹掌握Promise的使用,讀這篇就夠了(轉)
https://www.jianshu.com/p/fe5f173276bd Promise的重要性我認為我沒有必要多講,概括起來說就是必須得掌握,而且還要掌握透徹。這篇文章的開頭,主要跟大家分析一下,為什麼會有Promise出現。 在實際的使用當中,有非常多的應用場景我們不能立即知道應該如
CUDA進階第六篇-GPU資源(視訊記憶體、控制代碼等)管理
最近在加速一個影象演算法,符合《CUDA進階第五篇-如何估算出程式的GPU加速比》中的第二種情況,程式由核函式和GPU API實現,但是資源管理特別差,視訊記憶體和控制代碼在程式中使用時才申請。每次函式執行都要申請和釋放一遍,非常耗費時間。優化方案一:C++重構我想到的第一個
Mysql高手系列 - 第18篇:mysql流程控制語句詳解(高手進階)
Mysql系列的目標是:通過這個系列從入門到全面掌握一個高階開發所需要的全部技能。 這是Mysql系列第18篇。 環境:mysql5.7.25,cmd命令中進行演示。 程式碼中被[]包含的表示可選,|符號分開的表示可選其一。 上一篇儲存過程&自定義函式,對儲存過程和自定義函式做了一個簡單的介紹,但是如
SQL Server代理的階梯 - 第2級:作業步驟和子系列
backup 叫我 標記 指定 jobs microsoft 有效 soft 開發 作者:Richard Waymire,2017/10/11(第一版:2011/02/17) 原文鏈接:http://www.sqlservercentral.com/articles/SQL
史上最簡單的SpringCloud教程 | 第十三篇: 斷路器聚合監控(Hystrix Turbine)
打開 jsb cli fill alt 數據 需要 eap south 當我們有很多個服務的時候,這就需要聚合所以服務的Hystrix Dashboard的數據了。這就需要用到Spring Cloud的另一個組件了,即Hystrix Turbine。 看單個的Hystri
第十三篇:Spring Boot之郵件服務
傳送郵件應該是網站的必備功能之一,什麼註冊驗證,忘記密碼或者是給使用者傳送營銷資訊。最早期的時候我們會使用JavaMail的相關API來編寫傳送郵件的相關程式碼,後來Spring推出了JavaMailSender更加簡化了郵件傳送的過程,再之後Spring Boot對此進行了封裝就有了現
SpringBoot進階教程 | 第一篇:YML多文件塊實現多環境配置
你是否為SpringBoot一個功能多個yml和多個properties檔案區分不同執行環境配置,經常為這些配置檔案的管理而頭疼,現在通過這篇文章,將徹底解決你的煩惱,這篇文篇介紹,怎麼通過yml檔案構建多文件塊,區分不同環境配置,自由切換不同環境啟動專案,一個
一起來學SpringBoot | 第十三篇:RabbitMQ延遲佇列
SpringBoot 是為了簡化 Spring 應用的建立、執行、除錯、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關注業務本身而不是外部的XML配置,我們只需遵循規範,引入相關的依賴就可以輕易的搭建出一個 WEB 工程 初探Rabbi
【SSH三大框架】Hibernate基礎第十三篇:lazy、constrained、fetch三個屬性的作用和使用方法
這三個屬性,個人感覺對於懶載入是很重要的,所以又重新開了一篇部落格來寫下這三個屬性的作用和使用方法 一、lazy屬性: lazy概念:只有真正使用該物件時,才會建立。對於hibernate而言,真正使用時才會發出SQL語句 1、在集合中定義: <set name
SpringBoot非官方教程 | 第十三篇:springboot整合spring cache
本文介紹如何在springboot中使用預設的spring cache, 宣告式快取 Spring 定義 CacheManager 和 Cache 介面用來統一不同的快取技術。例如 JCache、 EhCache、 Hazelcast、 Guava、 Redi
【譯】索引進階(十):索引內部結構
最小 pan 了解 幹什麽 梳理 所有 部分 層級 子節點 在之前的系列文章中我們對索引進行了一個邏輯梳理,關註與它能為我們幹什麽。現在是時候對其進行一個物理上的分析並檢查索引的內部結構。只有理解了索引的內部我們才能夠理解索引的開銷。只有通過了解索引的內部結構以及它是如何維
Python 學習 第十三篇:數據的讀寫-文件、DataFrame、json和pymssql
獲得 use 字串 刪除 int parser write 大小 new Python的文件是一個重要的對象,使用open()函數來打開文件,創建文件對象,進行文件的讀寫操作。當數據用於交換信息時,通常需要把數據保存為有格式的文本數據,可以保存為有特定的行分隔符和列分隔符的
SpringBoot第十三篇:日誌處理
作者:追夢1819 原文:https://www.cnblogs.com/yanfei1819/p/10973583.html 版權宣告:本文為博主原創文章,轉載請附上博文連結! 引言 日誌是軟體系統的“基礎設施”,它可以幫助我們瞭解系統的執行軌跡,查詢系統的執行異常等。很多人都沒有引起對日誌的重視。
跟我學SpringCloud | 第十三篇:Spring Cloud Gateway服務化和過濾器
SpringCloud系列教程 | 第十三篇:Spring Cloud Gateway服務化和過濾器 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 上一篇文章服務閘道器 Spring Cloud G