จะลบข้อมูลขนาดใหญ่ของตารางใน SQL โดยไม่ต้องบันทึกได้อย่างไร


128

ฉันมีตารางข้อมูลขนาดใหญ่ มีข้อมูล 10 ล้านรายการในตารางนี้

วิธีที่ดีที่สุดสำหรับแบบสอบถามนี้คืออะไร

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

4
:) ฉันกลัวถ้าคุณไม่เต็มใจที่จะเขียน ETL บางประเภทเพื่อรับแถวทั้งหมด readTime> = dateadd (MONTH, -7, GETDATE ()) ในตารางอื่นจากนั้นออกตาราง Truncate และใส่ข้อมูลกลับโดยใช้ ETL คุณจะไม่สามารถป้องกันไม่ให้เขียนลงในบันทึกได้
TMNT2014

การบันทึกเป็นฟังก์ชันทั้งหมดหรือไม่มีเลยในการมีธุรกรรมที่ยืดหยุ่นได้ มันไม่สมเหตุสมผลที่จะไม่มีบันทึกสำหรับการดำเนินการบางอย่าง แต่ไม่ใช่บันทึกอื่นมิฉะนั้นบันทึกจะไร้ประโยชน์
Erik Philips

1
ส่งออกข้อมูลที่คุณต้องการเก็บไว้ตัดทอนตารางแล้วนำเข้าอีกครั้ง
โบฮีเมียน

อีกทางเลือกหนึ่งคือการใช้ tablevariable ซึ่งไม่ได้บันทึกไว้ ดังนั้นเก็บข้อมูล readTime> = dateadd (MONTH, -7, GETDATE ()) ไว้ในตัวแปรตารางแล้วตัดทอนตารางเดิมและคัดลอกข้อมูลกลับจากตัวแปรตาราง อย่างไรก็ตามฉันจะสำรองข้อมูลไว้ในกรณีที่มีบางอย่างผิดพลาดและตารางจะถูกตัดโดยไม่ได้ตั้งใจ :) และทำการทดสอบสคริปต์ของคุณในสภาพแวดล้อมที่น้อยกว่าเสมอ
TMNT2014

คำตอบ:


204
  1. หากคุณกำลังลบแถวทั้งหมดในตารางนั้นตัวเลือกที่ง่ายที่สุดคือ Truncate table เช่น

    TRUNCATE TABLE LargeTable
    GO

    Truncate table จะทำให้ตารางว่างเปล่าคุณไม่สามารถใช้ WHERE clause เพื่อ จำกัด แถวที่ถูกลบและจะไม่มีการเรียกใช้ทริกเกอร์

  2. ในทางกลับกันหากคุณกำลังลบข้อมูลมากกว่า 80-90 เปอร์เซ็นต์ให้บอกว่าคุณมีทั้งหมด 11 ล้านแถวและคุณต้องการลบ 10 ล้านอีกวิธีหนึ่งคือการแทรก 1 ล้านแถวเหล่านี้ (บันทึกที่คุณต้องการเก็บไว้ ) ไปยังตารางการแสดงละครอื่น ตัดทอนตารางขนาดใหญ่นี้และแทรกกลับ 1 ล้านแถวเหล่านี้

  3. หรือถ้าสิทธิ์ / มุมมองหรือวัตถุอื่น ๆ ที่มีตารางขนาดใหญ่นี้เป็นตารางพื้นฐานไม่ได้รับผลกระทบจากการวางตารางนี้คุณจะได้รับจำนวนแถวที่ค่อนข้างเล็กเหล่านี้ลงในตารางอื่นให้วางตารางนี้และสร้างตารางอื่นด้วยสคีมาเดียวกันและนำเข้าสิ่งเหล่านี้ แถวกลับเข้าไปในตารางอดีตขนาดใหญ่นี้

  4. ตัวเลือกสุดท้ายที่ฉันคิดได้คือเปลี่ยนฐานข้อมูลของคุณRecovery Mode to SIMPLEแล้วลบแถวเป็นกลุ่มเล็ก ๆ โดยใช้ while loop อะไรทำนองนี้ ..

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

และอย่าลืมเปลี่ยนโหมดการกู้คืนกลับเป็นโหมดเต็มและฉันคิดว่าคุณต้องสำรองข้อมูลเพื่อให้ได้ผลเต็มที่ (โหมดการเปลี่ยนแปลงหรือการกู้คืน)


14
โปรดจำไว้ด้วยว่าหากคุณตัดทอนตารางคุณจะไม่สามารถเชื่อมโยง FK ใด ๆ กับตารางได้
HLGEM

1
แต่จะแน่ใจได้อย่างไรว่าคุณกำลังลบข้อมูล 80-90%? สมมติว่าฉันมีเฉพาะช่วงของค่าที่ควรลบ และฉันมีไม่กี่โต๊ะ ดังนั้นฉันต้องตรวจสอบทุกอันและคำนวณเปอร์เซ็นต์และถ้าประมาณ 30% ฉันเดาว่าวิธีนี้ไม่ได้ผลมากนัก ... ฉันกำลังพยายามหาวิธีที่ดีที่สุดสำหรับกรณีที่ไม่รู้จัก
Archont

7
@ Archont optimal solution for unknown caseนั่นคือความฝันไม่ใช่เหรอ? น่าเสียดายที่คุณไม่สามารถรักษาทุกโรคได้ด้วยยาเม็ดเดียว ฉันได้แนะนำวิธีแก้ปัญหาที่เป็นไปได้สำหรับสถานการณ์ต่างๆ ไม่มีกระสุนเศษไม้ที่นี่น่าเสียดาย
M.Ali

5
สิ่งหนึ่งที่จะต้องทราบหากเลือกตัวเลือกที่ 4: ขึ้นอยู่กับวิธีการที่โต๊ะที่ใช้ก็อาจจะเป็นตัวเลือกที่ดีที่จะลบน้อยกว่า 5000 แถวในเวลาที่จะหลีกเลี่ยงการเพิ่มล็อค
Daniel

หากจำนวนระเบียนที่จะลบมีขนาดใหญ่กว่ามากดังนั้นระเบียนที่จะยังคงอยู่ในตารางฉันพบว่าการเลือกอย่างง่ายในตาราง temp ของระเบียนที่จะอยู่ในและวางตารางเดิมและการเปลี่ยนชื่อตารางชั่วคราวนั้นเร็วกว่ามาก ระบุว่าคุณไม่ได้ใช้คีย์ต่างประเทศของรหัสประจำตัว
Vladimir Bozic

96

คำตอบของ @ m-ali นั้นถูกต้อง แต่โปรดทราบว่าบันทึกอาจเพิ่มขึ้นได้มากหากคุณไม่ทำธุรกรรมหลังจากแต่ละชิ้นและทำการตรวจสอบ นี่คือวิธีที่ฉันจะทำและใช้บทความนี้http://sqlperformance.com/2013/03/io-subsystem/chunk-deletesเป็นข้อมูลอ้างอิงพร้อมการทดสอบประสิทธิภาพและกราฟ:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
นี่ควรเป็นคำตอบที่ยอมรับได้ในกรณีที่เนื้อที่ดิสก์ที่มีอยู่มี จำกัด ไม่มีCOMMIT TRANSACTIONและCHECKPOINTท่อนไม้ยังคงเติบโต ขอบคุณที่แจ้งเรื่องนี้ให้ชัดเจน
gkoul

+1 โปรดทราบว่าคุณอาจต้องการเปรียบเทียบ@Deleted_Rowsกับ 10,000 หรือคุณอาจจบลงด้วยการวนซ้ำไม่สิ้นสุดเนื่องจากการลบข้อมูลชุดเล็ก ๆ ไปเรื่อย ๆ ดังนั้นWHILE (@Deleted_Rows = 10000)- ทันทีที่ไม่มี "หน้า" เต็มของข้อมูลที่จะลบมันจะหยุดลง ในการนำไปใช้งานของคุณWHILE (@Deleted_Rows > 0)while-loop จะดำเนินการอีกครั้งแม้ว่าจะลบเพียงแถวเดียวก็ตามและการดำเนินการถัดไปอาจพบแถวหรือสองแถวที่จะลบซึ่งส่งผลให้เกิดการวนซ้ำแบบไม่สิ้นสุด
NS du Toit

@NSdu ไปที่คำสั่ง WHERE กำลังพิจารณาบันทึกที่มีอายุอย่างน้อย 7 เดือนดังนั้นจะไม่มีระเบียนใหม่ที่ตรงตามเงื่อนไขนั้นในขณะที่คุณกำลังดำเนินการลบ
Francisco Goldenstein

@FranciscoGoldenstein ดีวันที่ใช้ในการสอบถามจะแตกต่างกันกับแต่ละซ้ำในขณะที่คุณซ้ำ ๆ คำนวณวันภายในห่วงตัวเอง:WHILE dateadd(MONTH,-7,GETDATE())
NS du Toit

@FranciscoGoldenstein นอกจากนี้ยังอาจจะสำหรับกรณีการใช้งานอื่น ๆ กว่านี้ - อาจได้รับข้อมูลใหม่เพิ่มเข้าไปในตารางต้นแบบที่จะส่งผลในการบันทึกใหม่ที่สามารถลบออกระหว่างการทำซ้ำที่แตกต่างกันของWHILEวง
NS du Toit

52

คุณยังสามารถใช้ GO + กี่ครั้งที่คุณต้องการดำเนินการสืบค้นเดียวกัน

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

ฉันชอบสิ่งนี้มันใช้งานได้สำหรับฉันฉันแทรกแถวเดียวกันลงในตาราง 26 ล้านครั้งโดยไม่ได้ตั้งใจและจำเป็นต้องลบสิ่งที่เกิดขึ้นทั้งหมดซึ่งในคำสั่งลบเพียงครั้งเดียวหน่วยความจำบนเซิร์ฟเวอร์หมดดังนั้นนี่จึงเป็นคำถามที่ดี จะหยุดลูปกลางหรือไม่ถ้าไม่มีแถวที่จะลบ
ScottC

2
@ScottC ไม่ใช่วนซ้ำเพียงแค่ทำแบบสอบถามซ้ำ (batch like) และถ้าคุณหมดแถวก็ไม่สามารถลบอะไรได้ แต่มันจะไม่หยุด คุณจะได้รับผลกระทบบางอย่างเช่น (0 แถว) หากหมดแถวที่คุณลบ
Bunkerbuster

ใช่ฉันพบว่าประมาณ 5 นาทีหลังจากที่ฉันโพสต์คำถามของฉันเนื่องจากการลบของฉันเสร็จสิ้นขอบคุณสิ่งนี้มีประโยชน์มาก!
ScottC

1
จาก MS SQL Server ไวยากรณ์นี้GO xxควรจะทำงานจากอะไร ฉันได้รับข้อผิดพลาด"ไม่พบขั้นตอนการจัดเก็บ" " หากไม่มีGOคำสั่งมันก็ทำงานได้ดี
Abel

3
อืมดูเหมือนว่าฉันสามารถเรียกใช้งานได้และมันทำงานได้หลายครั้ง แต่ใน MS SQL Mgt Studio จะแสดงเส้นหยิกสีแดงพร้อมข้อผิดพลาดที่กล่าวถึง (แต่ F5-run ใช้งานได้แล้ว)
Abel

11

@ ฟรานซิสโกโกลเดนสไตน์แก้ไขเพียงเล็กน้อย ต้องใช้ COMMIT หลังจากที่คุณตั้งค่าตัวแปรมิฉะนั้น WHILE จะถูกดำเนินการเพียงครั้งเดียว:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

รูปแบบนี้ของM.Aliทำงานได้ดีสำหรับฉัน มันลบบางส่วนล้างบันทึกและทำซ้ำ ฉันเฝ้าดูบันทึกที่เติบโตปล่อยวางและเริ่มต้นใหม่

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

นี่มีประโยชน์มาก! ฉันแก้ไขเพื่อกำหนดพารามิเตอร์# of rowsเพื่อลบในแต่ละครั้งและWHEREประโยคด้วย ใช้งานได้เหมือนมีเสน่ห์!
พระอิศวร

7

หากคุณเต็มใจ (และสามารถ) ที่จะใช้การแบ่งพาร์ติชันนั่นเป็นเทคนิคที่มีประสิทธิภาพในการลบข้อมูลจำนวนมากโดยมีค่าใช้จ่ายในการรันไทม์เพียงเล็กน้อย ไม่คุ้มค่าสำหรับการออกกำลังกายครั้งเดียว


4

ผมสามารถที่จะลบ 19 ล้านแถวจากโต๊ะของเรา 21 ล้านแถวในไม่กี่นาที นี่คือแนวทางของฉัน

หากคุณมีคีย์หลักที่เพิ่มขึ้นโดยอัตโนมัติในตารางนี้คุณสามารถใช้คีย์หลักนี้ได้

  1. รับค่าต่ำสุดของคีย์หลักของตารางขนาดใหญ่ที่ readTime <dateadd (MONTH, -7, GETDATE ()) (เพิ่มดัชนีใน readTime หากยังไม่มีอยู่ดัชนีนี้จะถูกลบไปพร้อมกับตารางในขั้นตอนที่ 3) ให้เก็บไว้ในตัวแปร 'min_primary'

  2. แทรกแถวทั้งหมดที่มีคีย์หลัก> min_primary ลงในตารางการจัดเตรียม (ตารางหน่วยความจำหากไม่มีแถวไม่ใหญ่)

  3. วางโต๊ะขนาดใหญ่

  4. สร้างตารางใหม่ คัดลอกแถวทั้งหมดจากตารางการแสดงละครไปยังตารางหลัก

  5. วางตารางการแสดงละคร


3

คุณสามารถลบแบทช์ขนาดเล็กโดยใช้ while loop โดยมีลักษณะดังนี้:

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

การใช้งานอื่น:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

ไม่จำเป็น;

หากเปิดใช้งานบันทึกธุรกรรมให้ปิดใช้งานบันทึกธุรกรรม

ALTER DATABASE dbname SET RECOVERY SIMPLE;


1

หากคุณใช้ SQL server 2016 หรือสูงกว่าและหากตารางของคุณมีพาร์ติชันที่สร้างขึ้นตามคอลัมน์ที่คุณพยายามลบ (เช่นคอลัมน์ Timestamp) คุณสามารถใช้คำสั่งใหม่นี้เพื่อลบข้อมูลตามพาร์ติชัน

ตัดทอนตารางด้วย (PARTITIONS ({|} [, ... n]))

การดำเนินการนี้จะลบข้อมูลในพาร์ติชันที่เลือกเท่านั้นและควรเป็นวิธีที่มีประสิทธิภาพที่สุดในการลบข้อมูลจากส่วนหนึ่งของตารางเนื่องจากจะไม่สร้างบันทึกธุรกรรมและจะทำได้เร็วเท่ากับการตัดทอนปกติ แต่จะไม่มีการลบข้อมูลทั้งหมด จากโต๊ะ

ข้อเสียเปรียบคือถ้าตารางของคุณไม่ได้ตั้งค่าด้วยพาร์ติชันคุณต้องไปโรงเรียนเก่าและลบข้อมูลด้วยวิธีการปกติจากนั้นสร้างตารางใหม่ด้วยพาร์ติชันเพื่อให้คุณสามารถทำสิ่งนี้ได้ในอนาคตซึ่งเป็นสิ่งที่ฉันทำ ฉันเพิ่มการสร้างและลบพาร์ติชันลงในขั้นตอนการแทรกเอง ฉันมีตารางที่มี 500 ล้านแถวดังนั้นนี่จึงเป็นตัวเลือกเดียวที่จะลดเวลาในการลบ

สำหรับรายละเอียดเพิ่มเติมโปรดดูลิงค์ด้านล่าง: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

ตารางที่ตัดทอน 2016 เซิร์ฟเวอร์ SQL พร้อมพาร์ติชัน

ด้านล่างนี้คือสิ่งที่ฉันทำก่อนเพื่อลบข้อมูลก่อนที่ฉันจะสร้างตารางใหม่โดยมีพาร์ติชันที่มีข้อมูลที่ต้องการ แบบสอบถามนี้จะทำงานเป็นเวลาหลายวันในช่วงเวลาที่กำหนดจนกว่าข้อมูลจะถูกลบ

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

ถ้าฉันบอกว่าไม่มีลูปฉันสามารถใช้GOTOคำสั่งเพื่อลบบันทึกจำนวนมากโดยใช้เซิร์ฟเวอร์ sql EXA

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

ด้วยวิธีนี้คุณสามารถลบข้อมูลจำนวนมากโดยมีขนาดการลบน้อยลง

โปรดแจ้งให้เราทราบหากต้องการข้อมูลเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.