一些看似簡單的數據操作,當作用於海量數據集時,就會出現“意料之外,卻在情理之中”的問題,海量數據操作,需要采用特殊方法,才能“曲徑通幽”。在刪除海量數據時,需要注意日志的增長,索引碎片的增加和數據庫的恢復模式,特別是利用大容量日志操作,來減少日志的增長和提高數據插入的速度。對於大數據去重,通過一些小小的改進,比如創建索引,設置忽略重復值選項等,能夠提高去重的效率。
一,從海量數據中刪除數據
從海量數據表中刪除一半數據,看似簡單,使用delete命令,如果真這么干,SQL Server產生的事務日志暴增,估計會把服務器硬盤爆掉。數據庫的恢復模式會影響日志文件的增長,在刪除海量數據時,根據采用的方法,相應地把恢復模式設置為simple,或bulk_logged 模式,能夠在很大程度上減少刪除操作產生的事務日志,從而避免日志暴增。
另外,在刪除數據時,把表上的多余索引刪除(注意,是刪除多余的索引),只保留一個必需的索引;在數據刪除完成之后,再重建索引,能夠提高數據刪除操作的性能。有人做過實驗,從存儲1.6億條記錄的大表中刪除數據,每刪除400萬條要消耗1.5 - 3小時,越到后面速度越慢,為什么?這是因為,每次刪除數據時,數據庫都要相應地更新索引,這是很慢的硬盤 IO操作,並且,越到后面,索引碎片越多,更新索引就越慢,這就是在刪除400萬條記錄時,一開始只消耗1.5小時,后面要消耗3小時原因。
最后,根據保留數據占總數據量的比例,選擇不同的方法刪除數據。如果大表中保留的數據較少,可以先把保留的數據存儲到臨時表中,然后,把原始表刪除,這樣能夠利用大容量日志操作,來減少日志的增長和提高數據插入的速度。
1,循環刪除,避免日志文件暴增
在從海量數據表中刪除大量數據時,為了避免日志文件暴增,通常采用循環刪除方法:首先設置恢復模式為simple,然后每次刪除操作都只刪除部分數據,這樣,當單個刪除操作執行完成時,事務日志會被及時清理,事務日志一般保持單個刪除操作的事務日志量。
循環刪除的偽代碼如下,該方法仍有一些局限性,耗時過長,並且會長期使數據庫處於簡單恢復模式下:
--ALTER DATABASE database_name SET RECOVERY SIMPLE ; while @index<@EndIndex begin delete table_name where index<=@index; set @index+=@Increment end
2,將數據插入到臨時表中,把原表drop
如果原始表有一半以上的數據要被刪除,從原始表中執行delete命令刪除數據,效率十分低下,可以考慮,把原始表中的數據通過select語句篩選出來,然后批量插入導新表中,這種方式利用了大容量日志(Bulk Logged)操作的優勢。由於 SELECT INTO,INSERT SELECT 是大容量日志操作,select命令不會產生大量日志文件,因此,執行插入比執行刪除的效率更高。最后,執行drop命令,刪除整個原始表,幾乎不消耗任何時間。
--ALTER DATABASE database_name SET RECOVERY BULK_LOGGED ; insert into new_table select column_list from original_table where filter_retain drop table original_table
把臨時表重命名,執行 sp_rename 或手動重命名,其中 @objtype 參數是可選的,默認值是NULL,對表重命名,設置參數 @objtype='object':
sp_rename [ @objname = ] 'object_name' , [ @newname = ] 'new_name' [ , [ @objtype = ] 'object_type' ]
3,對分區表執行分區轉移操作
SQL Server的分區表實際上是一系列物理上獨立存儲的“表”(也叫做分區)構成的,如果要刪除的數據位於同一個分區,或者,一個分區中的數據都需要被刪除,那么可以把該分區轉移(switch)到一個臨時表中,由於分區的轉移僅僅是元數據庫的變更,因此,不會產生任何的數據IO,分區轉移瞬間完成。被剝離的分區,通過drop命令刪除,整個過程僅僅會產生少量的IO操作,用於元數據變更;而不會產生用於數據刪除的IO操作,這種方法,耗時最短,資源消耗最小,效率最高。
alter table original_table SWITCH PARTITION source_partition_number TO temporary_table drop table temporary_table
二,從海量數據中去重
數據去重,分為部分列去重和全部列去重,全部列去重,使用distinct子句來實現,由於distinct操作符會創建在tempdb中臨時表,因此,distinct操作是IO密集型的操作。而部分列去重,一般采用row_number排名函數來實現,也可以考慮使用忽略重復值的唯一索引來實現。在實際的項目開發中,部分列去重更為常見。
1,使用row_number函數來實現
選擇排名函數,是因為排名函數有部分列分區排序的功能:首先在部分列上創建索引,這樣數據庫引擎能夠根據索引列快速排序,然后通過row_number函數和cte來實現重復數據的刪除。在數據去重時,需要注意,如果刪除的數據量太大,數據庫引擎會產生大量的事務日志,導致日志文件暴增,在選擇該方法時,需要慎重。
create index index_name on table_name ( index_columns ) with(data_compression=page); with cte as ( select index_columns, row_number() over(partition by index_columns order by ...) as rn from table_name ) delete from cte where rn>1
2,使用忽略重復值的唯一索引來實現
通過插入和忽略重復值實現部分列的去重,相對來說,更容易控制,用戶可以通過循環插入方式來執行,這樣,在單獨的一個事務中,控制插入數據的數量,能夠控制產生的事務日志不至於太大,對於海量數據的去重,建議采用該方法。
創建一個臨時表,在部分列上創建忽略重復值的唯一索引:
create unique index index_name on new_table ( index_columns ) with(ignore_dup_key=on)
由於SQL Server不允許在包含重復值的數據表上創建唯一索引,因此,必須創建一個新的空表,新表時原始表的結構的復制,在部分列上創建忽略重復值的唯一索引。在執行插入操作時, IGNORE_DUP_KEY 選項會忽略重復的索引鍵值,並拋出警告(Warning)。