1. 程式人生 > >大資料操作:刪除和去重

大資料操作:刪除和去重

一些看似簡單的資料操作,當作用於海量資料集時,就會出現“意料之外,卻在情理之中”的問題,海量資料操作,需要採用特殊方法,才能“曲徑通幽”。在刪除海量資料時,需要注意日誌的增長,索引碎片的增加和資料庫的恢復模式,特別是利用大容量日誌操作,來減少日誌的增長和提高資料插入的速度。對於大資料去重,通過一些小小的改進,比如建立索引,設定忽略重複值選項等,能夠提高去重的效率。

一,從海量資料中刪除資料

從海量資料表中刪除一半資料,看似簡單,使用delete命令,如果真這麼幹,SQL Server產生的事務日誌暴增,估計會把伺服器硬碟爆掉。

1.  資料庫的恢復模式會影響日誌檔案的增長,在刪除海量資料時,根據採用的方法,相應地把恢復模式設定為simple,或bulk_logged 模式,能夠在很大程度上減少刪除操作產生的事務日誌,從而避免日誌暴增。

2.  在刪除資料時,把表上的多餘索引刪除(注意,是刪除多餘的索引),只保留一個必需的索引;在資料刪除完成之後,再重建索引,能夠提高資料刪除操作的效能。有人做過實驗,從儲存1.6億條記錄的大表中刪除資料,每刪除400萬條要消耗1.5 - 3小時,越到後面速度越慢,為什麼?這是因為,每次刪除資料時,資料庫都要相應地更新索引,這是很慢的硬碟 IO操作,並且,越到後面,索引碎片越多,更新索引就越慢,這就是在刪除400萬條記錄時,一開始只消耗1.5小時,後面要消耗3小時原因。

3.  根據保留資料佔總資料量的比例,選擇不同的方法刪除資料。如果大表中保留的資料較少,可以先把保留的資料儲存到臨時表中,然後,把原始表刪除,這樣能夠利用大容量日誌操作,來減少日誌的增長和提高資料插入的速度。

1,迴圈刪除,避免日誌檔案暴增

在從海量資料表中刪除大量資料時,為了避免日誌檔案暴增,通常採用迴圈刪除方法:首先設定恢復模式為simple,然後每次刪除操作都只刪除部分資料,這樣,當單個刪除操作執行完成時,事務日誌會被及時清理,事務日誌一般保持單個刪除操作的事務日誌量。

迴圈刪除的虛擬碼如下,該方法仍有一些侷限性,耗時過長,並且會長期使資料庫處於簡單恢復模式下:

--ALTER DATABASE database_name SET RECOVERY SIMPLE ;  

while @index<@EndIndex
begin
    delete table_name 
    where index<
[email protected]
; set @[email protected] end

2,將資料插入到臨時表中,把原表truncate

如果原始表有一半以上的資料要被刪除,從原始表中執行delete命令刪除資料,效率十分低下,可以考慮,把原始表中的資料通過select語句篩選出來,然後批量插入導新表中,這種方式利用了大容量日誌(Bulk Logged)操作的優勢。由於 SELECT INTO,INSERT SELECT 是大容量日誌操作,select命令不會產生大量日誌檔案,因此,執行插入比執行刪除的效率更高。最後,執行drop命令,刪除整個原始表,幾乎不消耗任何時間。

--ALTER DATABASE database_name SET RECOVERY BULK_LOGGED ;  

insert into new_tableselect column_list from original_table where filter_retain

drop table original_table

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)。