第一梯次中間下課休息時,有位學員詢問我一個問題
學員:請問執行Delete,資料表大小應該會降低吧。
RiCo:大部分是這樣。
學員:但我公司資料庫有些資料表刪除資料後,資料表反而變得更肥,而且效能也變差,怎麼會這樣呢?
RiCo:你資料表有LOB資料類型或寬欄位嗎?
學員:有的,前人設計就是這樣。
RiCo:那你有用 Trigger嗎?
學員:老師你從來不用Trigger協同處理資料嗎?
從我接觸SQL SERVER以來,我一直不是Trigger愛好者(程度大概和巢狀交易相當吧),
為什麼呢?因為Trigger協同處理資料會讓簡單的事情變複雜(讓SQL SERVER自動幫你處理事情,
有時是需要付出昂貴代價的),我在SQL SERVER這條路上,
始終保持3KS(Keep Simple、Keep Stupid、Keep Study),
當你理解背後行為過程,其實很多規畫和設計都要依當時環境和需求來處理,
所以我有時不會走所謂的"正常道路",雖然Trigger還是有好處,
但我一隻手就可以數出來,anyway~~該學員問題主因是頁面分割引起,
這裡我示範Trigger如何造成頁面分割,另外關於頁面分割你能回答以下問題嗎?
- 什麼操作會引起頁面分割?
- 頁面分割會造成什麼影響?
- 交易rollback,頁面分割也會rollback嗎?
- 頁面分割會耗用記憶體資源嗎?
- 僅資料頁會發生頁面分割嗎?
Note:我blog有幾篇提到頁面分割(可參考最後連結)
大部分系統一定有刪除需求,而真實世界,資料類型選擇經常是被忽略的環節,
我得到多數答案如下:
- 開發時程短,所以資料表先這樣。
- 寫商業邏輯比較重要,功能需求先達到,後面有時間、有問題再來改資料類型。
- 避免資料長度不一或後續修改麻煩甚至觀念錯誤,一開始選擇寬欄位(nvarchar(4000)、varchar(8000)..等)或LOB。
- 效能問題交給DBA,我只重視需求是否符合(我深知"職責分離"的好處~XD)。
- 前人設計就是這樣,我不能輕易修改的(我理解"敵不動我不動"的道理~XD)。
但偏偏多數人又喜愛用Trigger,也因這樣無形中就埋下效能地雷,
這篇讓我簡單重現Trigger After Delete,如何造成我厭惡的頁面分割。
我的Trigger很懶惰,資料刪除後不做任何事情
-刪除後,很簡單的不做任何事情
create trigger UF_TR_Del
on dbo.testPageSplitDel
AFTER DELETE
as return
執行刪除作業前
--查看頁面分割數量
select COUNT_BIG(*) as [PageSplits]
from sys.fn_dblog(NULL,NULL) fdblog
where fdblog.[Transaction Name] = N'SplitPage';
--查看資料表 data page資訊
select ips.index_type_desc,
ips.alloc_unit_type_desc,
ips.page_count,
ips.record_count,
ips.max_record_size_in_bytes
from sys.dm_db_index_physical_stats(
DB_ID(), OBJECT_ID(N'dbo.testPageSplitDel', N'U'), NULL, NULL, 'DETAILED') AS ips
where ips.index_level = 0
Pagecount:31。
--查看緩衝區中page資訊
select t3.[file_id],
t3.page_id,
t3.page_type,
t3.row_count,
t3.free_space_in_bytes,
t3.is_modified
from sys.partitions AS t1
join sys.allocation_units AS t2
on t2.container_id = t1.hobt_id
join sys.dm_os_buffer_descriptors AS t3 ON
t3.allocation_unit_id = t2.allocation_unit_id
where t1.[object_id] = OBJECT_ID(N'dbo.testPageSplitDel', N'U')
order by t3.page_id;
每一page空白大小:21。
執行刪除作業後
--Delete datas
delete testPageSplitDel
where ID % 20 = 0;
查詢資料表
select * from testPageSplitDel where id>=100 and id<3000
上面測試資料表大小看起來很正常,但下面我將修改欄位長度,符合我看到的真實世界。
drop table testPageSplitDel
-- Test table
create table dbo.testPageSplitDel
(
ID int identity(1,1) PRIMARY KEY,
c1 nvarchar(50) NULL,
c2 varchar(8000) NULL,--長度由4000改為8000
)
--執行刪除後查看頁面分割和page資訊
-- Delete datas
delete testPageSplitDel
where ID % 20 = 0;
查詢資料
select * from testPageSplitDel where id>=100 and id<3000
這就是執行刪除作業後,資料表大小不減反增的主因。
我建立的Trigger在資料刪除後沒做任何事情,但同樣刪除500筆資料操作,
第二次測試卻在改變欄位大小後,
資料表大小不減反增,相關作業效能一路走下坡,
這些看似正常的行為,卻往往隱藏效能地雷,如果DBA或開發人員觀念錯誤,
更是容易引爆(魔鬼總是藏在細節裡)。
另為什麼我更改欄位長度會發生頁面分割?
很多人不是說,新增或更新資料才會發生頁面分割,為什麼刪除資料也會發生頁面分割?
刪除資料後,為什麼資料表大小沒有立即減少?SQL SERVER在偷懶嗎?
上面問題答案你可以從我的blog找到,
因為這絕對不是Bug ,且SQL SERVER也沒在偷懶 :) 。
頁面分割答案如下:
1.什麼操作會引起頁面分割?
Ans:新增、更新、刪除、查詢(特例)。
2.頁面分割會造成什麼影響?
Ans:內部碎片、page變多,進而影響效能。
3.交易rollback,頁面分割也會rollback嗎?
Ans:不會。
4.頁面分割會耗用記憶體資源嗎?
Ans:會。因為是在記憶體中處理且成本昂貴。
5.僅資料頁會發生頁面分割嗎?
Ans:否,索引頁也會發生。
結論:
Trigger是萬惡源頭。
參考
[SQL SERVER][Memo]再談 Clustered Index
[SQL SERVER][Performance]小心使用With NoLock
[SQL SERVER]小心使用With NoLock(續)
[SQL SERVER][Memo]頁面分割影響交易記錄檔大小
[SQL SERVER][Maintain]管理交易記錄檔(5)