1. 程式人生 > >SQL Server系列之 刪除大量數據

SQL Server系列之 刪除大量數據

創建 base res ram 表數 mit 解決 shrink creat

一、寫在前面 - 想說愛你不容易   為了升級數據庫至SQL Server 2008 R2,拿了一臺現有的PC做測試,數據庫從正式庫Restore(3個數據庫大小誇張地達到100G+),而機器內存只有可憐的4G,不僅要承擔DB Server角色,同時也要作為Web Server,可想而知這臺機器的命運是及其慘烈的,只要MS SQL Server一啟動,內存使用率立馬飆升至99%。沒辦法,只能升內存,兩根8G共16G的內存換上,結果還是一樣,內存瞬間被秒殺(CPU利用率在0%徘徊)。由於是PC機,內存插槽共倆,目前市面上最大的單根內存為16G(價格1K+),就算買回來估計內存還是不夠(臥槽,PC機傷不起啊),看樣子別無它法 -- 刪數據!!!   刪除數據 - 說的容易, 不就是DELETE嗎?靠,如果真這麽幹,我XXX估計能“知道上海淩晨4點的樣子”(KB,Sorry,誰讓我是XXX的Programmer,哥在這方面絕對比你牛X),而且估計會暴庫(磁盤空間不足,產生的日誌文件太大了)。 二、沙場點兵 - 眾裏尋他千百度   為了更好地闡述我所遇到的困難和問題,有必要做一些必要的測試和說明,同時這也是對如何解決問題的一種探究。因為畢竟這個問題的根本是如何來更好更快的操作數據,說到底就是DELETE、UPDATE、INSERT、TRUNCATE、DROP等的優化操作組合,我們的目的就是找出最優最快最好的方法。為了便於測試,準備了一張測試表Employee 復制代碼 --Create table Employee CREATE TABLE [dbo].[Employee] ( [EmployeeNo] INT PRIMARY KEY, [EmployeeName] [nvarchar](50) NULL, [CreateUser] [nvarchar](50) NULL, [CreateDatetime] [datetime] NULL ); 復制代碼 1. 數據插入PK 1.1. 循環插入,執行時間為38026毫秒 復制代碼 --循環插入 SET STATISTICS TIME ON; DECLARE @Index INT = 1; DECLARE @Timer DATETIME = GETDATE(); WHILE @Index <= 100000 BEGIN INSERT [dbo].[Employee](EmployeeNo, EmployeeName, CreateUser, CreateDatetime) VALUES(@Index, ‘Employee_‘ + CAST(@Index AS CHAR(6)), ‘system‘, GETDATE()); SET @Index = @Index + 1; END SELECT DATEDIFF(MS, @Timer, GETDATE()) AS [執行時間(毫秒)]; SET STATISTICS TIME OFF; 復制代碼 1.2. 事務循環插入,執行時間為6640毫秒 復制代碼 --事務循環 BEGIN TRAN; SET STATISTICS TIME ON; DECLARE @Index INT = 1; DECLARE @Timer DATETIME = GETDATE(); WHILE @Index <= 100000 BEGIN INSERT [dbo].[Employee](EmployeeNo, EmployeeName, CreateUser, CreateDatetime) VALUES(@Index, ‘Employee_‘ + CAST(@Index AS CHAR(6)), ‘system‘, GETDATE()); SET @Index = @Index + 1; END SELECT DATEDIFF(MS, @Timer, GETDATE()) AS [執行時間(毫秒)]; SET STATISTICS TIME OFF; COMMIT; 復制代碼 1.3. 批量插入,執行時間為220毫秒 復制代碼 SET STATISTICS TIME ON; DECLARE @Timer DATETIME = GETDATE(); INSERT [dbo].[Employee](EmployeeNo, EmployeeName, CreateUser, CreateDatetime) SELECT TOP(100000) EmployeeNo = ROW_NUMBER() OVER (ORDER BY C1.[OBJECT_ID]), ‘Employee_‘, ‘system‘, GETDATE() FROM SYS.COLUMNS AS C1 CROSS JOIN SYS.COLUMNS AS C2 ORDER BY C1.[OBJECT_ID] SELECT DATEDIFF(MS, @Timer, GETDATE()) AS [執行時間(毫秒)]; SET STATISTICS TIME OFF; 復制代碼 1.4. CTE插入,執行時間也為220毫秒 復制代碼 SET STATISTICS TIME ON; DECLARE @Timer DATETIME = GETDATE(); ;WITH CTE(EmployeeNo, EmployeeName, CreateUser, CreateDatetime) AS( SELECT TOP(100000) EmployeeNo = ROW_NUMBER() OVER (ORDER BY C1.[OBJECT_ID]), ‘Employee_‘, ‘system‘, GETDATE() FROM SYS.COLUMNS AS C1 CROSS JOIN SYS.COLUMNS AS C2 ORDER BY C1.[OBJECT_ID] ) INSERT [dbo].[Employee] SELECT EmployeeNo, EmployeeName, CreateUser, CreateDatetime FROM CTE; SELECT DATEDIFF(MS, @Timer, GETDATE()) AS [執行時間(毫秒)]; SET STATISTICS TIME OFF; 復制代碼 小結: 按執行時間,效率依次為:CTE和批量插入效率相當,速度最快,事務插入次之,單循環插入速度最慢; 單循環插入速度最慢是由於INSERT每次都有日誌,事務插入大大減少了寫入日誌次數,批量插入只有一次日誌,CTE的基礎是CLR,善用速度是最快的。 2. 數據刪除PK 2.1. 循環刪除,執行時間為1240毫秒 復制代碼 SET STATISTICS TIME ON; DECLARE @Timer DATETIME = GETDATE(); DELETE FROM [dbo].[Employee]; SELECT DATEDIFF(MS, @Timer, GETDATE()) AS [執行時間(毫秒)]; SET STATISTICS TIME OFF; 復制代碼 2.2. 批量刪除,執行時間為106毫秒 復制代碼 SET STATISTICS TIME ON; DECLARE @Timer DATETIME = GETDATE(); SET ROWCOUNT 100000; WHILE 1 = 1 BEGIN BEGIN TRAN DELETE FROM [dbo].[Employee]; COMMIT IF @@ROWCOUNT = 0 BREAK; END SET ROWCOUNT 0; SELECT DATEDIFF(MS, @Timer, GETDATE()) AS [執行時間(毫秒)]; SET STATISTICS TIME OFF; 復制代碼 2.3. TRUNCATE刪除,執行時間為0毫秒 復制代碼 SET STATISTICS TIME ON; DECLARE @Timer DATETIME = GETDATE(); TRUNCATE TABLE [dbo].[Employee]; SELECT DATEDIFF(MS, @Timer, GETDATE()) AS [執行時間(毫秒)]; SET STATISTICS TIME OFF; 復制代碼 小結: TRUNCATE太快了,清除10W數據一點沒壓力,批量刪除次之,最後的DELTE太慢了; TRUNCATE快是因為它屬於DDL語句,只會產生極少的日誌,普通的DELETE不僅會產生日誌,而且會鎖記錄。 三、磨刀霍霍 - 猶抱琵琶半遮面   由上面的第二點我們知道,插入最快和刪除最快的方式分別是批量插入和TRUNCATE,所以為了達到刪除大數據的目的,我們也將采用這兩種方式的組合,其中心思想是先把需要保留的數據存放之新表中,然後TRUNCATE原表中的數據,最後再批量把數據插回去,當然實現方式也可以隨便變通。 1. 保留需要的數據之新表中->TRUNCATE原表數據->還原之前保留的數據之原表中   腳本類似如下 SELECT * INTO #keep FROM Original WHERE CreateDate > ‘2011-12-31‘ TRUNCATE TABLE Original INSERT Original SELECT * FROM #keep   第一條語句會把所有要保留的數據先存放至表#keep中(表#keep無需手工創建,由SELECT INTO生效),#keep會Copy原始表Original的表結構。PS:如果你只想創建表結構,但不拷貝數據,則對應的腳本如下 SELECT * INTO #keep FROM Original WHERE 1 = 2   第二條語句用於清除整個表中數據,產生的日誌文件基本可以忽略;第三條語句用於還原保留數據。 幾點說明: 你可以不用SELECT INTO,自己通過寫腳本(或拷貝現有表)來創建#keep,但是後者有一個弊端,即無法通過SQL腳本來獲得對應的表生成Script(我的意思是和原有表完全一致的腳本,即基本列,屬性,索引,約束等),而且當要操作的表比較多時,估計你肯定會抓狂; 既然第一點欠妥,那考慮新建一個同樣的數據庫怎麽樣?既可以使用現有腳本,而且生成的數據庫基本一致,但是我告訴你最好別這麽做,因為第一要跨庫,第二,你得準備足夠的磁盤空間。 2. 新建表結構->批量插入需要保留的數據->DROP原表->重命名新表為原表   CREATE TABLE #keep AS (xxx) xxx -- 使用上面提到的方法(使用既有表的創建腳本),但是不能夠保證完全一致;   INSERT #keep SELECT * FROM Original where clause   DROP TBALE Original   EXEC SP_RENAME ‘#keep‘,‘Original‘   這種方式比第一種方法略快點,因為省略了數據還原(即最後一步的數據恢復),但是稍微麻煩點,因為你需要創建一張和以前原有一模一樣的表結構,包括基本列、屬性、約束、索性等等。 三、數據收縮 - 秋風少落葉   數據刪除後,發現數據庫占用空間大小並沒有發生變化,此時我們就用借助強悍的數據收縮功能了,腳本如下,運行時間不定,取決於你的數據庫大小,多則幾十分鐘,少則瞬間秒殺 DBCC SHRINKDATABASE(DB_NAME)

SQL Server系列之 刪除大量數據