SQL Server系列之 刪除大量數據
阿新 • • 發佈:2017-09-11
創建 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系列之 刪除大量數據