แทรกอัปเดต proc ที่จัดเก็บไว้บน SQL Server


104

ฉันได้เขียน proc ที่เก็บไว้ซึ่งจะทำการอัปเดตหากมีบันทึกอยู่มิฉะนั้นจะทำการแทรก มีลักษณะดังนี้:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

ตรรกะของฉันที่อยู่เบื้องหลังการเขียนด้วยวิธีนี้คือการอัปเดตจะทำการเลือกโดยปริยายโดยใช้ where clause และถ้าสิ่งนั้นส่งกลับ 0 ดังนั้นการแทรกจะเกิดขึ้น

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

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

คำตอบ:


61

สมมติฐานของคุณถูกต้องนี่เป็นวิธีที่ดีที่สุดในการทำและเรียกว่าการเพิ่ม / รวม upsert

ความสำคัญของ UPSERT - จาก sqlservercentral.com :

สำหรับการอัปเดตทุกครั้งในกรณีที่กล่าวถึงข้างต้นเราจะลบการอ่านเพิ่มเติมหนึ่งรายการออกจากตารางหากเราใช้ UPSERT แทน EXISTS น่าเสียดายสำหรับการแทรกทั้งวิธี UPSERT และ IF EXISTS ใช้จำนวนการอ่านบนโต๊ะเท่ากัน ดังนั้นการตรวจสอบการมีอยู่ควรทำก็ต่อเมื่อมีเหตุผลที่ถูกต้องมากในการปรับ I / O เพิ่มเติม วิธีที่ดีที่สุดในการทำสิ่งต่างๆคือตรวจสอบให้แน่ใจว่าคุณมีการอ่านน้อยที่สุดในฐานข้อมูล

กลยุทธ์ที่ดีที่สุดคือพยายามอัปเดต หากไม่มีแถวที่ได้รับผลกระทบจากการอัปเดตให้แทรก ในสถานการณ์ส่วนใหญ่แถวนั้นจะมีอยู่แล้วและจะต้องใช้ I / O เพียงแถวเดียว

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


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

54

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

สำหรับคำตอบด่วนให้ลองทำตามรูปแบบต่อไปนี้ จะทำงานได้ดีบน SQL 2000 ขึ้นไป SQL 2005 ช่วยให้คุณจัดการข้อผิดพลาดซึ่งจะเปิดตัวเลือกอื่น ๆ และ SQL 2008 ให้คำสั่ง MERGE แก่คุณ

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

1
ในบล็อกโพสต์ของคุณคุณสรุปด้วยการใช้คำใบ้ WITH (การอัพเดต, ต่อลำดับได้) ในการตรวจสอบการมีอยู่ อย่างไรก็ตามการอ่าน MSDN เป็นสถานะ: "UPDLOCK - ระบุว่าการล็อกการอัปเดตจะถูกดำเนินการและเก็บไว้จนกว่าธุรกรรมจะเสร็จสมบูรณ์" นี่หมายความว่าคำใบ้ต่ออนุกรมนั้นไม่จำเป็นหรือไม่เนื่องจากการล็อกการอัปเดตจะถูกระงับไว้สำหรับส่วนที่เหลือของธุรกรรมหรือฉันเข้าใจอะไรผิด
Dan Def

10

ถ้าจะใช้กับ SQL Server 2000/2005 รหัสเดิมจะต้องอยู่ในธุรกรรมเพื่อให้แน่ใจว่าข้อมูลยังคงสอดคล้องกันในสถานการณ์จำลองพร้อมกัน

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

ซึ่งจะต้องเสียค่าใช้จ่ายด้านประสิทธิภาพเพิ่มเติม แต่จะทำให้ข้อมูลมีความสมบูรณ์

เพิ่มตามที่แนะนำไปแล้วควรใช้ MERGE เมื่อมีให้


8

MERGE เป็นหนึ่งในคุณสมบัติใหม่ใน SQL Server 2008 โดยวิธีนี้


และคุณควรใช้มันอย่างแน่นอนแทนที่จะเป็นเรื่องไร้สาระเกี่ยวกับโฮมบรูว์ที่อ่านยากนี้ ตัวอย่างดีๆอยู่ที่นี่ - mssqltips.com/sqlservertip/1704/…
Rich Bryant

6

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

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

บางทีการเพิ่มการตรวจสอบข้อผิดพลาด @@ และการย้อนกลับอาจเป็นความคิดที่ดี


@Munish Goyal เนื่องจากในฐานข้อมูลคำสั่งและคำสั่งหลายคำสั่งทำงานใน paralel จากนั้นเธรดอื่นสามารถแทรกแถวได้หลังจากรันการอัปเดตและก่อนที่จะรันการแทรก
Tomas Tintera

5

หากคุณไม่ได้ทำการผสานใน SQL 2008 คุณต้องเปลี่ยนเป็น:

ถ้า @@ rowcount = 0 และ @@ error = 0

มิฉะนั้นหากการอัปเดตล้มเหลวด้วยเหตุผลบางประการโปรแกรมจะพยายามแทรกในภายหลังเนื่องจากจำนวนแถวในคำสั่งที่ล้มเหลวคือ 0


3

แฟนตัวยงของ UPSERT ลดรหัสในการจัดการ นี่เป็นอีกวิธีหนึ่งที่ฉันทำ: หนึ่งในพารามิเตอร์อินพุตคือ ID ถ้า ID เป็น NULL หรือ 0 คุณจะรู้ว่าเป็น INSERT มิฉะนั้นจะเป็นการอัปเดต ถือว่าแอปพลิเคชันรู้ว่ามี ID หรือไม่ดังนั้นจะไม่ทำงานในทุกสถานการณ์ แต่จะตัดการดำเนินการลงครึ่งหนึ่งหากคุณทำ


2

แก้ไขโพสต์ Dima Malenko:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

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


1

ตรรกะของคุณดูเหมือนจะฟังดูดี แต่คุณอาจต้องการพิจารณาเพิ่มรหัสบางอย่างเพื่อป้องกันการแทรกหากคุณได้ส่งผ่านคีย์หลักที่เฉพาะเจาะจง

มิฉะนั้นหากคุณทำการแทรกอยู่เสมอหากการอัปเดตไม่มีผลกับบันทึกใด ๆ จะเกิดอะไรขึ้นเมื่อมีคนลบบันทึกก่อนที่คุณ "UPSERT" จะทำงาน ตอนนี้บันทึกที่คุณพยายามอัปเดตไม่มีอยู่ดังนั้นจะสร้างบันทึกแทน นั่นอาจไม่ใช่พฤติกรรมที่คุณกำลังมองหา

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