การทำธุรกรรมที่ชัดเจนจำเป็นต้องใช้ในขณะนี้ในวง?


11

SQL Server 2014:

เรามีตารางที่มีขนาดใหญ่มาก (100 ล้านแถว) และเราจำเป็นต้องอัปเดตเขตข้อมูลสองสามแห่ง

สำหรับบันทึกการจัดส่ง ฯลฯ เรายังต้องการเก็บไว้ในธุรกรรมที่มีขนาดพอดี

หากเราปล่อยให้บิตรันด้านล่างแล้วยกเลิก / ยุติการค้นหางานที่ทำไปทั้งหมดจะถูกส่งไปหรือเราจำเป็นต้องเพิ่มคำสั่ง BEGIN TRANSACTION / END TRANSACTION เพื่อให้เราสามารถยกเลิกได้ตลอดเวลา?

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END

คำตอบ:


13

งบส่วนบุคคล - DML, DDL, ฯลฯ - เป็นการทำธุรกรรมในตัวเอง ใช่หลังจากการวนซ้ำแต่ละครั้ง (ในทางเทคนิค: หลังจากแต่ละคำสั่ง) สิ่งที่UPDATEคำสั่งนั้นเปลี่ยนจะถูกกำหนดโดยอัตโนมัติ

แน่นอนว่ามีข้อยกเว้นเสมอใช่ไหม เป็นไปได้ที่จะเปิดใช้งานธุรกรรมโดยนัยผ่านSET IMPLICIT_TRANSACTIONSซึ่งในกรณีนี้UPDATEคำสั่งแรกจะเริ่มทำธุรกรรมที่คุณจะต้องทำCOMMITหรือROLLBACKสิ้นสุด นี่คือการตั้งค่าระดับเซสชันที่ปิดโดยปริยายในกรณีส่วนใหญ่

เราจำเป็นต้องเพิ่มคำสั่ง BEGIN TRANSACTION / END TRANSACTION ที่ชัดเจนเพื่อให้เราสามารถยกเลิกได้ตลอดเวลาหรือไม่

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


นอกจากนี้คุณอาจต้องการตั้งค่า@CHUNK_SIZEให้มีจำนวนน้อยลง การเพิ่มการล็อคโดยทั่วไปเกิดขึ้นที่การล็อค 5,000 ครั้งที่วัตถุหนึ่ง ขึ้นอยู่กับขนาดของแถวและหากกำลังทำการล็อกแถวกับการล็อกหน้าคุณอาจกำลังเกินขีด จำกัด นั้น หากขนาดของแถวนั้นมีขนาดเพียง 1 หรือ 2 แถวพอดีกับแต่ละหน้าคุณอาจกดปุ่มนี้แม้ว่าจะทำการล็อกหน้าก็ตาม

หากตารางถูกแบ่งพาร์ติชันแล้วคุณจะมีตัวเลือกในการตั้งค่าLOCK_ESCALATIONตัวเลือก (นำมาใช้ใน SQL Server 2008) สำหรับตารางAUTOที่จะล็อคพาร์ติชันเท่านั้นและไม่ใช่ทั้งตารางเมื่อเลื่อนระดับ หรือสำหรับตารางใด ๆ คุณสามารถตั้งค่าตัวเลือกเดียวกันDISABLEนั้นได้แม้ว่าคุณจะต้องระวังให้มาก ดูการเปลี่ยนแปลงตารางสำหรับรายละเอียด

นี่คือเอกสารบางส่วนที่พูดถึงการเลื่อนระดับการล็อคและเกณฑ์: การเลื่อนการล็อค (ซึ่งมีการระบุว่าใช้กับ "SQL Server 2008 R2 และรุ่นที่สูงกว่า") และนี่คือบล็อกโพสต์ที่เกี่ยวข้องกับการตรวจสอบและแก้ไขการเพิ่มล็อค: ล็อคใน Microsoft SQL Server (ตอนที่ 12 - ล็อก)


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

  1. สำหรับลูปของคุณการทำWHILE (@@ROWCOUNT = @CHUNK_SIZE)ดีกว่าเล็กน้อยเนื่องจากถ้าจำนวนแถวที่อัพเดตในการทำซ้ำครั้งล่าสุดน้อยกว่าจำนวนที่ร้องขอเป็น UPDATE ดังนั้นจึงไม่มีงานเหลือให้ทำ

  2. ถ้าdeletedข้อมูลเป็นBITประเภทข้อมูลแล้วไม่ว่าค่าที่กำหนดโดยไม่ว่าจะเป็นหรือไม่deletedDateเป็น2000-01-01? ทำไมคุณถึงต้องการทั้งสองอย่าง?

  3. หากทั้งสองฟิลด์เป็นฟิลด์ใหม่และคุณเพิ่มเข้าไปเนื่องจากNULLอาจเป็นการดำเนินการออนไลน์ / ไม่มีการบล็อกและตอนนี้ต้องการอัปเดตเป็นค่า "เริ่มต้น" แล้วนั่นไม่จำเป็น เริ่มต้นใน SQL Server 2012 (Enterprise Edition เท่านั้น) การเพิ่มNOT NULLคอลัมน์ที่มีข้อ จำกัด DEFAULT เป็นการดำเนินการที่ไม่บล็อกตราบใดที่ค่า DEFAULT เป็นค่าคงที่ ดังนั้นหากคุณยังไม่ได้ใช้ฟิลด์เพียงแค่วางและเพิ่มอีกครั้งNOT NULLและด้วยข้อ จำกัด เริ่มต้น

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

    1. สร้างตารางชั่วคราว (#FullSet) ที่เพิ่งมีฟิลด์คีย์จากดัชนีคลัสเตอร์ในนั้น
    2. สร้างตารางชั่วคราวที่สอง (#CurrentSet) ของโครงสร้างเดียวกัน
    3. แทรกลงใน #FullSet ผ่าน SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;

      ที่TOP(n)อยู่ในนั้นเนื่องจากขนาดของตาราง ด้วย 100 ล้านแถวในตารางคุณไม่จำเป็นต้องเติมตารางคิวด้วยชุดคีย์ทั้งหมดนั้นโดยเฉพาะอย่างยิ่งถ้าคุณวางแผนที่จะหยุดกระบวนการทุก ๆ ครั้งและเริ่มต้นใหม่ในภายหลัง ดังนั้นอาจตั้งค่าnเป็น 1 ล้านและปล่อยให้สิ่งนั้นผ่านไปสู่ความสำเร็จ คุณสามารถกำหนดเวลานี้ได้ในงานตัวแทน SQL ที่รันชุด 1 ล้าน (หรืออาจน้อยกว่า) จากนั้นรอเวลาถัดไปที่กำหนดเวลาเพื่อรับอีกครั้ง จากนั้นคุณสามารถกำหนดเวลาให้ทำงานทุก ๆ 20 นาทีดังนั้นจะมีห้องหายใจที่บังคับใช้ระหว่างชุดของnแต่มันจะยังคงเสร็จสิ้นกระบวนการทั้งหมดแบบอัตโนมัติ จากนั้นให้ลบงานออกเมื่อไม่มีอะไรให้ทำ :-)

    4. ในลูปทำ:
      1. เติมชุดปัจจุบันผ่านสิ่งที่ชอบ DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
      2. IF (@@ROWCOUNT = 0) BREAK;
      3. ทำการอัปเดตโดยใช้บางสิ่งเช่น: UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
      4. ล้างชุดปัจจุบัน: TRUNCATE TABLE #CurrentSet;
  5. ในบางกรณีจะช่วยเพิ่มดัชนีตัวกรองเพื่อช่วยให้SELECTฟีดนั้นลงใน#FullSetตารางอุณหภูมิ ต่อไปนี้เป็นข้อควรพิจารณาบางประการเกี่ยวกับการเพิ่มดัชนีดังกล่าว:
    1. เงื่อนไข WHERE ควรตรงกับเงื่อนไข WHERE ของข้อความค้นหาของคุณ WHERE deleted is null or deletedDate is null
    2. ที่จุดเริ่มต้นของกระบวนการแถวส่วนใหญ่จะตรงกับเงื่อนไข WHERE ของคุณดังนั้นดัชนีไม่เป็นประโยชน์ คุณอาจต้องการรอจนกว่าจะมีเครื่องหมายประมาณ 50% ก่อนที่จะเพิ่มสิ่งนี้ แน่นอนว่าจะช่วยได้มากแค่ไหนและเมื่อใดควรเพิ่มดัชนีให้แตกต่างกันไปเนื่องจากปัจจัยหลายประการดังนั้นจึงเป็นการลองผิดลองถูก
    3. คุณอาจต้องอัปเดตสถานะด้วยตนเองและ / หรือสร้างดัชนีใหม่ด้วยตนเองเพื่อให้ทันสมัยเนื่องจากข้อมูลฐานมีการเปลี่ยนแปลงค่อนข้างบ่อย
    4. โปรดจำไว้ว่าดัชนีในขณะที่ช่วยเหลือSELECTนั้นจะทำให้เกิดความเสียหายUPDATEเนื่องจากเป็นวัตถุอื่นที่ต้องได้รับการอัปเดตในระหว่างการดำเนินการดังนั้นจึงมี I / O มากขึ้น สิ่งนี้เล่นได้ทั้งสองอย่างโดยใช้ดัชนีตัวกรอง (ซึ่งย่อขนาดเมื่อคุณอัปเดตแถวเนื่องจากแถวที่ตรงกับตัวกรองน้อยลง) และรอสักครู่เพื่อเพิ่มดัชนี (ถ้ามันจะไม่เป็นประโยชน์อย่างยิ่งในการเริ่มต้น I / O เพิ่มเติม)

อัปเดต:โปรดดูคำตอบของคำถามที่เกี่ยวข้องกับคำถามนี้สำหรับการนำไปใช้อย่างสมบูรณ์ของสิ่งที่แนะนำข้างต้นรวมถึงกลไกในการติดตามสถานะและยกเลิกอย่างหมดจด: เซิร์ฟเวอร์ sql: อัปเดตฟิลด์บนโต๊ะขนาดใหญ่ในกลุ่มเล็ก ๆ : วิธีการ ความคืบหน้า / สถานะ?


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

@BaconBits เห็นด้วยกับการเริ่มต้นง่าย ๆ เพื่อความเป็นธรรมคำแนะนำเหล่านี้ไม่ได้มีวัตถุประสงค์เพื่อใช้กับทุกสถานการณ์ คำถามเกี่ยวกับการจัดการกับตารางที่มีขนาดใหญ่มาก (100 ล้านแถว)
โซโลมอน Rutzky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.