ปรับปรุงประสิทธิภาพที่ไม่มีข้อมูลเปลี่ยนแปลง


31

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

ตัวอย่างเช่นจะมีความแตกต่างในความเร็วการดำเนินการระหว่าง UPDATE 1 และ UPDATE 2 ในสิ่งต่อไปนี้:

CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
    (1, 1),
    (2, 2),
    (3, 3);

-- UPDATE 1
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2
    AND Value <> 2;
SELECT @@ROWCOUNT;

-- UPDATE 2
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2;
SELECT @@ROWCOUNT;

DROP TABLE MyTable;

เหตุผลที่ฉันถามคือฉันต้องการจำนวนแถวเพื่อรวมแถวที่ไม่เปลี่ยนแปลงเพื่อให้ฉันรู้ว่าจะทำการแทรกถ้าไม่มี ID เช่นนี้ฉันใช้แบบฟอร์ม UPDATE 2 หากมีประโยชน์ด้านประสิทธิภาพในการใช้แบบฟอร์ม UPDATE 1 เป็นไปได้หรือไม่ที่จะนับจำนวนแถวที่ฉันต้องการอย่างใด


ดูsqlperformance.com/2012/10/t-sql-queries/conditional-updates (แม้ว่าฉันไม่ได้บันทึกกรณีและปัญหาที่ไม่มีค่าเปลี่ยนแปลง)
Aaron Bertrand

คำตอบ:


24

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

อาจเป็นเพราะมีความแตกต่างของประสิทธิภาพเล็กน้อยเนื่องจากUPDATE 1 :

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

อย่างไรก็ตามคุณจำเป็นต้องวัดความแตกต่างของจำนวนเท่าใดในระบบของคุณด้วยสคีมาของคุณและข้อมูลและโหลดระบบ มีปัจจัยหลายอย่างที่ส่งผลกระทบต่อ UPDATE ที่ไม่ได้อัปเดตที่มีผลกระทบ:

  • จำนวนของการช่วงชิงบนโต๊ะที่กำลังอัพเดต
  • จำนวนแถวที่มีการปรับปรุง
  • หากมีทริกเกอร์ UPDATE ในตารางที่กำลังอัปเดต (ตามที่ระบุไว้โดยทำเครื่องหมายในความคิดเห็นในคำถาม) หากคุณดำเนินUPDATE TableName SET Field1 = Field1การ Update Trigger จะเริ่มทำงานและระบุว่ามีการอัปเดตฟิลด์ (หากคุณตรวจสอบโดยใช้ฟังก์ชันUPDATE ()หรือCOLUMNS_UPDATED ) และฟิลด์ในทั้งสองINSERTEDและDELETEDตารางเป็นค่าเดียวกัน

นอกจากนี้ส่วนสรุปต่อไปนี้พบได้ในบทความของพอลไวท์เรื่องThe Impact of Non-Updating Updates (ตามที่ระบุไว้โดย @spaghettidba ในความคิดเห็นเกี่ยวกับคำตอบของเขา):

SQL Server มีจำนวนของการปรับให้เหมาะสมเพื่อหลีกเลี่ยงการบันทึกที่ไม่จำเป็นหรือการล้างหน้าเมื่อประมวลผลการดำเนินการ UPDATE ที่จะไม่ส่งผลให้เกิดการเปลี่ยนแปลงใด ๆ กับฐานข้อมูลถาวร

  • โดยทั่วไปการไม่อัปเดตการอัปเดตไปยังตารางคลัสเตอร์จะหลีกเลี่ยงการบันทึกเพิ่มเติมและการล้างหน้ายกเว้นคอลัมน์ที่มีรูปแบบ (ส่วนหนึ่ง) คีย์คลัสเตอร์จะได้รับผลกระทบจากการดำเนินการอัปเดต
  • หากส่วนใด ๆ ของคีย์คลัสเตอร์นั้น 'อัพเดท' เป็นค่าเดียวกันการดำเนินการจะถูกบันทึกราวกับว่าข้อมูลมีการเปลี่ยนแปลงและหน้าเว็บที่ได้รับผลกระทบจะถูกทำเครื่องหมายว่าสกปรกในพูลบัฟเฟอร์ นี่คือผลลัพธ์ของการแปลง UPDATE เป็นการดำเนินการลบแล้วแทรก
  • ตารางฮีปจะทำงานเหมือนกับตารางที่ทำคลัสเตอร์ยกเว้นจะไม่มีคีย์คลัสเตอร์เพื่อทำให้การบันทึกเพิ่มเติมหรือการล้างหน้า กรณีนี้ยังคงอยู่แม้ว่าจะมีคีย์หลักที่ไม่ได้ทำคลัสเตอร์อยู่บนฮีป การไม่อัปเดตการอัปเดตไปที่ฮีปจึงมักหลีกเลี่ยงการบันทึกและการล้างข้อมูลเพิ่มเติม (แต่ดูด้านล่าง)
  • ทั้งฮีปและตารางคลัสเตอร์จะได้รับการบันทึกและฟลัชพิเศษสำหรับแถวใด ๆ ที่คอลัมน์ LOB ที่มีข้อมูลมากกว่า 8000 ไบต์ได้รับการอัปเดตเป็นค่าเดียวกันโดยใช้ไวยากรณ์ใด ๆ นอกเหนือจาก 'SET column_name = column_name'
  • เพียงแค่เปิดใช้งานระดับการแยกการกำหนดเวอร์ชันของแถวทั้งสองบนฐานข้อมูลจะทำให้เกิดการบันทึกและล้างข้อมูลเพิ่มเติมเสมอ สิ่งนี้เกิดขึ้นโดยไม่คำนึงถึงระดับการแยกที่มีผลสำหรับธุรกรรมการอัพเดท

โปรดทราบ (โดยเฉพาะอย่างยิ่งถ้าคุณไม่ติดตามลิงค์เพื่อดูบทความเต็มของ Paul) รายการสองรายการต่อไปนี้:

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

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


... ฉันต้องการจำนวนแถวเพื่อรวมแถวที่ไม่เปลี่ยนแปลงดังนั้นฉันจึงรู้ว่าจะแทรกถ้าไม่มี ID อยู่หรือไม่ ... เป็นไปได้ไหมที่ฉันจะนับแถวที่ฉันต้องการอย่างใด

หากคุณจัดการกับแถวเดียวคุณสามารถทำสิ่งต่อไปนี้:

UPDATE MyTable
SET    Value = 2
WHERE  ID = 2
AND Value <> 2;

IF (@@ROWCOUNT = 0)
BEGIN
  IF (NOT EXISTS(
                 SELECT *
                 FROM   MyTable
                 WHERE  ID = 2 -- or Value = 2 depending on the scenario
                )
     )
  BEGIN
     INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
     VALUES (2, 2);
  END;
END;

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

ฉันแสดงการใช้งานพื้นฐานในคำตอบต่อไปนี้:

วิธีหลีกเลี่ยงการใช้แบบสอบถามแบบผสานเมื่อทำซ้ำหลายข้อมูลโดยใช้พารามิเตอร์ xml

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


อัปเดต 1:

ในที่สุดฉันก็สามารถทำการทดสอบได้และนี่คือสิ่งที่ฉันพบเกี่ยวกับบันทึกธุรกรรมและการล็อก ก่อนคีมาสำหรับตาราง:

CREATE TABLE [dbo].[Test]
(
  [ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
  [StringField] [varchar](500) NULL
);

ถัดไปการทดสอบจะอัปเดตฟิลด์เป็นค่าที่มีอยู่แล้ว:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117

ผล:

-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
8 - IX          6 - PAGE
5 - X           7 - KEY

ในที่สุดการทดสอบที่กรองการอัพเดตเนื่องจากค่าไม่เปลี่ยนแปลง:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117
AND    rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';

ผล:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
7 - IU          6 - PAGE
4 - U           7 - KEY

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

นอกจากนี้การล็อกทรัพยากร PAGE และ KEY ก็มีข้อ จำกัด น้อยลงเมื่อกรองแถวที่ไม่ได้เปลี่ยนแปลง หากไม่มีกระบวนการอื่นใดที่โต้ตอบกับตารางนี้แสดงว่ามันอาจจะไม่ใช่ประเด็น (แต่มีความเป็นไปได้จริงหรือ โปรดทราบว่าการทดสอบที่แสดงในบล็อกที่เชื่อมโยงใด ๆ (และแม้แต่การทดสอบของฉัน) ถือว่าโดยนัยว่าไม่มีการโต้แย้งบนโต๊ะเนื่องจากไม่เคยเป็นส่วนหนึ่งของการทดสอบ การบอกว่าการอัพเดทที่ไม่ได้อัพเดตนั้นมีน้ำหนักเบาจนไม่ต้องจ่ายเงินในการทำการกรองจะต้องใช้เกลือเม็ดหนึ่งเนื่องจากการทดสอบเสร็จแล้วมากหรือน้อยในสุญญากาศ แต่ในการผลิตตารางนี้ส่วนใหญ่จะไม่โดดเดี่ยว แน่นอนว่าอาจเป็นได้ว่าการบันทึกเล็กน้อยและการล็อคที่ จำกัด มากขึ้นไม่ได้แปลว่ามีประสิทธิภาพน้อยลง ดังนั้นแหล่งข้อมูลที่น่าเชื่อถือที่สุดที่จะตอบคำถามนี้? เซิร์ฟเวอร์ SQL โดยเฉพาะ:เซิร์ฟเวอร์ SQL ของคุณ มันจะแสดงวิธีที่ดีกว่าสำหรับระบบของคุณ :-)


อัปเดต 2:

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

DECLARE @CurrentValue VARCHAR(500) = NULL,
        @NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
        @ID INT = 4082117;

SELECT @CurrentValue = rt.StringField
FROM   dbo.Test rt
WHERE  rt.ID = @ID;

IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
  -- row does not exist
  INSERT INTO dbo.Test (ID, StringField)
  VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
  -- row exists, so check value to see if it is different
  IF (@CurrentValue <> @NewValue)
  BEGIN
    -- value is different, so do the update
    UPDATE rt
    SET    rt.StringField = @NewValue
    FROM   dbo.Test rt
    WHERE  rt.ID = @ID;
  END;
END;

ผล:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (2 Lock:Acquired events):
Mode            Type
--------------------------------------
6 - IS          5 - OBJECT
6 - IS          6 - PAGE

ดังนั้นจึงมีเพียง 2 ล็อคที่ได้รับแทนที่จะเป็น 3 และล็อคทั้งสองนี้เป็น Intent Shared ไม่ใช่ Intent eXclusive หรือ Intent Update ( ความเข้ากันได้ของล็อค ) โปรดทราบว่าการล็อกแต่ละครั้งที่ได้รับจะได้รับการปล่อยตัวด้วยการล็อกแต่ละครั้งเป็นการดำเนินการ 2 ครั้งดังนั้นวิธีการใหม่นี้จึงเป็นการรวม 4 การดำเนินการแทนที่จะเป็นการดำเนินการ 6 ครั้งในวิธีการที่เสนอไว้ครั้งแรก เมื่อพิจารณาการดำเนินการนี้จะทำงานหนึ่งครั้งทุกๆ 15 มิลลิวินาที (ประมาณตามที่ระบุโดย OP) นั่นคือประมาณ 66 ครั้งต่อวินาที ดังนั้นข้อเสนอดั้งเดิมจึงมีจำนวนการดำเนินการล็อค / ปลดล็อก 396 ต่อวินาทีในขณะที่วิธีการใหม่นี้มีจำนวนการดำเนินการล็อค / ปลดล็อกเพียง 264 ต่อวินาทีของการล็อคน้ำหนักที่เบากว่า นี่ไม่ใช่การรับประกันประสิทธิภาพที่ยอดเยี่ยม แต่ก็คุ้มค่ากับการทดสอบ :-)


14

ซูมออกเล็กน้อยและคิดถึงภาพที่ใหญ่ขึ้น ในโลกแห่งความเป็นจริงข้อความปรับปรุงของคุณจะมีลักษณะเช่นนี้:

UPDATE MyTable
  SET Value = 2
WHERE
     ID = 2
     AND Value <> 2;

หรือมันจะเป็นแบบนี้มากขึ้น:

UPDATE Customers
  SET AddressLine1 = '123 Main St',
      AddressLine2 = 'Apt 24',
      City = 'Chicago',
      State = 'IL',
      (and a couple dozen more fields)
WHERE
     ID = 2
     AND (AddressLine1 <> '123 Main St'
     OR AddressLine2 <> 'Apt 24'
     OR City <> 'Chicago'
     OR State <> 'IL'
      (and a couple dozen more fields))

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

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

เนื่องจากความซับซ้อนเหล่านั้นจึงไม่เหมาะสมที่จะทำการเปรียบเทียบแบบแถวต่อแถวแบบฟิลด์ต่อฟิลด์เมื่อคุณทำการอัปเดต คิดว่าเป็นการทำงานแบบตั้งแทน


1
ตัวอย่างในโลกแห่งความเป็นจริงของฉันนั้นเรียบง่าย แต่เรียกได้ว่ามากมาย ประมาณการของฉันคือทุกๆ 15ms ในช่วงเวลาเร่งด่วน ฉันสงสัยว่า SQL Server นั้นดีพอที่จะไม่เขียนลงดิสก์เมื่อไม่ต้องการหรือไม่
Martin Brown

3

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

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อดูการอัปเดตที่ไม่อัปเดตโดย Paul White



1

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

IF EXISTS (Select 1 from Table where ID =@ID AND HashValue=Sha256(column1+column2))
GOTO EXIT
ELSE
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.