Update操作一定是先Delete再Insert嗎?
Update在數據庫中的執行是怎么樣的?“Update操作是先把數據刪除,然后再插入數據”。在網上看了很多也都是這么認為的。但在查閱到一些不同看法的時候我進行了一些驗證,發現還有其它的情況。
這里我分三種情況來講:
1、更改沒有索引列的字段,更改前和更改后的字符串長度一樣;
2、更改沒有索引列的字段,更改后比更改前的字符串長;
3、更改聚集索引字段。
先准備好數據,以便后面進行對比。
一、創建表、索引和數據:
--創建表MyTable1 IF EXISTS(SELECT * FROM sysobjects WHERE [name]='MyTable1' AND [type]='u') DROP TABLE MyTable1 GO CREATE TABLE MyTable1 ( ID INT, SName VARCHAR(20) ); --創建索引 CREATE UNIQUE CLUSTERED INDEX IX_ID ON MyTable1(ID); INSERT INTO MyTable1 values( 1,'aaaa') INSERT INTO MyTable1 values( 2,'bbbb') INSERT INTO MyTable1 values( 3,'cccc') GO SELECT * FROM MyTable1 mt GO --創建表MyTable2 IF EXISTS(SELECT * FROM sysobjects WHERE [name]='MyTable2' AND [type]='u') DROP TABLE MyTable2 GO CREATE TABLE MyTable2 ( ID INT, SName VARCHAR(20) ); --創建索引 CREATE UNIQUE CLUSTERED INDEX IX_ID ON MyTable2(ID); INSERT INTO MyTable2 VALUES ( 1,'aaaa') INSERT INTO MyTable2 VALUES ( 2,'bbbb') INSERT INTO MyTable2 VALUES ( 3,'cccc') SELECT * FROM MyTable2 mt
二、查看數據庫的ID號以及兩個表對應的ID
--查看數據庫的ID號以及兩個表對應的ID select db_id() AS '數據庫ID',object_id('MyTable1')as '表MyTable1_ID',object_id('MyTable2')as '表MyTable2_ID'
查詢結果如下:
三、查看數據頁的頁碼
--查看數據頁的ID DBCC extentinfo(6,213575799)--這里是剛剛查出來的數據庫的ID,是表MyTable1的ID DBCC extentinfo(6,229575856)--這里是剛剛查出來的數據庫的ID,是表MyTable2的ID
查詢結果如下:
表示MyTable1的數據存儲在第45頁,MyTable2的數據存儲在第94頁。
四、查看2個表所在頁面上每條記錄的存儲情況:
DBCC traceon(3604) WITH NO_INFOMSGS --打開跟蹤 DBCC IND('TestDB','MyTable1',0) --列出所有頁和索引。 --參數說明,:數據庫名;:表名;:索引的ID,表示堆,-1 表示顯示所有索引和IAMs, -2表示只顯示IAMs DBCC PAGE(TestDB,1,45,1) --查看數據頁和索引 --參數說明,:數據庫名;:數據頁文件文件組編號;:數據頁ID;:數據顯示類型(,)
語句執行后我們得到下面的結果:
- MyTable1的Row – Offset:
OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 115 (0x73) 0 (0x0) - 96 (0x60)
2、MyTable2的Row – Offset:
OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 115 (0x73) 0 (0x0) - 96 (0x60)
可以看到兩個表的存儲在數據庫中數據頁的位置是一樣的。這是因為一個頁只能放一個對象。
五、下面我們來看第一種情況:更改沒有索引列的字段,更改前和更改后的字符串長度一樣;
UPDATE MyTable1 SET SName = 'dddd' WHERE ID=2 OFFSET TABLE:
Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 115 (0x73) 0 (0x0) - 96 (0x60)
發現他的存儲位置沒有發生改變。
再來看MyTable2
--先刪除后插入 DELETE FROM MyTable2 WHERE ID=2 INSERT INTO MyTable2(ID,SName)VALUES(2, 'dddd')
OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 153 (0x99) 0 (0x0) - 96 (0x60)
表MyTable2的存儲發生變化了,原先在115和134之間存儲的是第二條記錄,現在這條記錄卻存儲到了153個字節以后了,而原來115和134之間什么也沒存儲,這樣這里就形成了內部碎片。對於這種update后數據的存儲位置不發生變化的更新稱為現場更新,如果位置發生了改變就稱為非現場更新。
所以對於這種情形來說:update操作並不是先delete后insert的。
六、下面我們再來測試第二種情況:更改沒有索引列的字段,更改后比更改前的字符串長;
先更新表MyTable1,再查看數據頁的存儲情況:
UPDATE MyTable1 SET SName='aaaaaa' WHERE ID=2 DBCC PAGE(TestDB,1,45,1)
存儲結果如下:
OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 153 (0x99) 0 (0x0) - 96 (0x60)
這時我們看到他的存儲和先Delete再Insert一樣了。
七、我們再來看第三種情況:更改聚集索引字段
為了避免對數據庫的操作影響查看的難度,再執行一下創建表的語句。
數據更新之前的結果如下:
MyTable1: OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 115 (0x73) 0 (0x0) - 96 (0x60)
MyTable2: OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 115 (0x73) 0 (0x0) - 96 (0x60)
先對表MyTable1操作:更新ID(即更新聚集索引列)
UPDATE MyTable1 SET ID = 0 WHERE ID=2 DBCC PAGE(TestDB,1,94,1) --數據頁的位置已經發生改變
存儲結果如下:
OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 153 (0x99) 0 (0x0) - 96 (0x60)
再來對MyTable2操作:
DELETE FROM MyTable2 WHERE ID = 2 INSERT INTO MyTable2(ID,SName)VALUES(2, 'bbbb') DBCC PAGE(TestDB,1,126,1) --數據頁的位置已經發生改變
存儲結果如下:
OFFSET TABLE: Row - Offset 2 (0x2) - 134 (0x86) 1 (0x1) - 153 (0x99) 0 (0x0) - 96 (0x60)
發現此時upadte為非現場更新,數據的存儲位置已經發生了改變,和我們所想的先Delete再Insert是一樣的。
其實在更改聚集索引鍵列的時候,也可能發生現場更新。
比如有3條記錄分別為1、2、5,我們把其中的2更改為了3,由於3是在1和5之間的數字,所以在更改為3后,這條記錄還是會存儲在1和5之間,所以就是現場更新了。