โซลูชั่นสำหรับ INSERT หรือ UPDATE บน SQL Server


598

MyTable(KEY, datafield1, datafield2...)สมมติโครงสร้างตาราง

บ่อยครั้งที่ฉันต้องการอัปเดตระเบียนที่มีอยู่หรือแทรกระเบียนใหม่หากไม่มีอยู่

เป็นหลัก:

IF (key exists)
  run update command
ELSE
  run insert command

วิธีที่ดีที่สุดในการเขียนสิ่งนี้คืออะไร?



27
สำหรับทุกคนที่เจอคำถามนี้เป็นครั้งแรก - โปรดอ่านคำตอบและความคิดเห็นของพวกเขาทั้งหมด บางครั้งอายุอาจนำไปสู่ข้อมูลที่ทำให้เข้าใจผิด ...
Aaron Bertrand

1
ลองใช้ตัวดำเนินการ EXCEPT ซึ่งเปิดตัวใน SQL Server 2005
Tarzan

คำตอบ:


370

อย่าลืมเกี่ยวกับการทำธุรกรรม ประสิทธิภาพดี แต่วิธีง่าย ๆ (IF EXISTS .. ) นั้นอันตรายมาก
เมื่อมีหลายกระทู้ที่จะพยายามแทรกหรืออัปเดตคุณสามารถได้รับการละเมิดคีย์หลักได้อย่างง่ายดาย

โซลูชั่นที่ให้บริการโดย @Beau Crawford & @Esteban แสดงแนวคิดทั่วไป แต่เกิดข้อผิดพลาดได้ง่าย

เพื่อหลีกเลี่ยงการหยุดชะงักและการละเมิด PK คุณสามารถใช้สิ่งนี้:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

หรือ

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

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

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

7
@aku ด้วยเหตุผลใดก็ตามที่คุณใช้คำใบ้ของตาราง ("กับ (xxxx)") ตรงข้ามกับ "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE" ก่อน BEGIN TRAN ของคุณ?
EBarr

4
@CashCow ผู้ชนะคนสุดท้ายนี่คือสิ่งที่ INSERT หรือ UPDATE ควรจะทำ: เม็ดมีดอันแรกอันที่สองจะอัพเดตเรคคอร์ด การเพิ่มการล็อคอนุญาตให้สิ่งนี้เกิดขึ้นในกรอบเวลาที่สั้นมากป้องกันข้อผิดพลาด
Jean Vincent

1
ฉันคิดเสมอว่าการใช้คำใบ้ในการล็อคไม่ดีและเราควรปล่อยให้ Microsoft Internal engine สั่งการให้ล็อค นี่เป็นข้อยกเว้นที่ชัดเจนสำหรับกฎหรือไม่

381

ดูคำตอบโดยละเอียดของฉันสำหรับคำถามก่อนหน้านี้ที่คล้ายกันมาก

@Beau ของ Crawfordเป็นวิธีที่ดีใน SQL 2005 และด้านล่าง แต่ถ้าคุณอนุญาตให้ตัวแทนมันควรจะไปกับผู้ชายคนแรกที่จะดังนั้นจึง ปัญหาเดียวก็คือสำหรับส่วนแทรกนั้นยังคงมีการดำเนินการ IO สองรายการ

MS Sql2008 แนะนำmergeจากมาตรฐาน SQL: 2003:

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

ตอนนี้เป็นการดำเนินการ IO เพียงอย่างเดียว แต่รหัสอันยิ่งใหญ่ :-(


10
@ Ian Boyd - ใช่นั่นคือไวยากรณ์มาตรฐานของ SQL: 2003 ไม่ใช่upsertผู้ให้บริการฐานข้อมูลรายอื่น ๆ เลยตัดสินใจที่จะสนับสนุนแทน upsertไวยากรณ์เป็นวิธีที่ไกลดีกว่าการทำเช่นนี้เพื่อที่ MS อย่างน้อยที่สุดควรจะได้รับการสนับสนุนมันเกินไป - มันไม่เหมือนว่ามันจะเป็นเพียงคำหลักที่ไม่ได้มาตรฐานใน T-SQL
คี ธ

1
ความคิดเห็นเกี่ยวกับคำใบ้ล็อคในคำตอบอื่น ๆ ? (จะพบเร็ว ๆ นี้ แต่ถ้าหากมันเป็นวิธีที่แนะนำผมขอแนะนำให้เพิ่มไว้ในคำตอบ)
eglasius

25
ดูที่นี่weblogs.sqlteam.com/dang/archive/2009/01/31/…สำหรับคำตอบเกี่ยวกับวิธีการป้องกันสภาวะการแข่งขันที่ก่อให้เกิดข้อผิดพลาดที่อาจเกิดขึ้นแม้จะใช้MERGEไวยากรณ์
Seph

5
@Seph เป็นเรื่องที่น่าประหลาดใจจริงๆ - เป็นความล้มเหลวของ Microsoft ที่นั่น: -SI เดาได้ว่าหมายความว่าคุณต้องมีHOLDLOCKการดำเนินการผสานในสถานการณ์ที่เกิดขึ้นพร้อมกันสูง
Keith

11
คำตอบนี้จำเป็นต้องได้รับการอัปเดตเพื่อแสดงความคิดเห็นโดย Seph เกี่ยวกับว่าไม่ได้ปลอดภัยต่อเธรดถ้าไม่มี HOLDLOCK ตามการโพสต์ที่เชื่อมโยง MERGE จะล็อกการอัพเดตโดยปริยาย แต่จะเผยแพร่ก่อนที่จะแทรกแถวซึ่งอาจทำให้เกิดสภาพการแข่งขันและการละเมิดคีย์หลักในการแทรก โดยใช้ HOLDLOCK ล็อคจะถูกเก็บไว้จนกว่าจะเกิดการแทรก
Triynko

169

ทำ UPSERT:

อัพเดต MyTable SET FieldA = @ FieldA WHERE Key = @ Key

IF @@ ROWCOUNT = 0
   ใส่เข้าไปใน MyTable (FieldA) ค่า (@FieldA)

http://en.wikipedia.org/wiki/Upsert


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

19
@Triynko ฉันคิดว่า @Sam Saffron หมายความว่าหากมีสองเธรด + แทรกสอดในลำดับที่ถูกต้องเซิร์ฟเวอร์ sql จะโยนข้อผิดพลาดที่ระบุว่ามีการละเมิดคีย์หลักจะเกิดขึ้น การห่อในธุรกรรมที่ต่อเนื่องกันได้เป็นวิธีที่ถูกต้องในการป้องกันข้อผิดพลาดในชุดคำสั่งด้านบน
EBarr

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

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

93

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

http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

แม้จะมีไวยากรณ์ "เรียบง่าย" นี้ฉันยังคงชอบวิธีนี้ (ไม่จัดการข้อผิดพลาดเนื่องจากความกะทัดรัด):

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;

ผู้คนมากมายจะแนะนำวิธีนี้:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
  INSERT ...
END
COMMIT TRANSACTION;

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

คนอื่น ๆ จะแนะนำวิธีนี้:

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

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


3
สิ่งที่เกี่ยวกับการแทรก / ปรับปรุงจากตาราง tem ที่แทรก / ปรับปรุงหลาย ๆ บันทึก?
user960567

@ user960567 ดีUPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);
Aaron Bertrand

4
ดีตอบหลังจากมากกว่า 2 ปี :)
user960567

12
@ user960567 ขออภัยฉันไม่ได้รับการแจ้งเตือนความคิดเห็นแบบเรียลไทม์
Aaron Bertrand

60
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

แก้ไข:

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


6
ฉันยังคงชอบอันนี้ดีกว่า ดูเหมือนว่าการเขียนโปรแกรมโดยใช้ผลข้างเคียงที่รุนแรงมากขึ้นและฉันไม่เคยเห็นดัชนีที่กระจุกตัวเล็ก ๆ ของตัวเลือกเริ่มต้นนั้นเพื่อก่อให้เกิดปัญหาประสิทธิภาพการทำงานในฐานข้อมูลจริง
Eric Z Beard

38

หากคุณต้องการที่จะ UPSERT มากกว่าหนึ่งบันทึกในเวลาที่คุณสามารถใช้ ANSI SQL: 2003 งบ DML รวม

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

ตรวจสอบงบผสานการเลียนแบบใน SQL Server 2005


1
ใน Oracle การออกคำสั่ง MERGE ฉันคิดว่าล็อคตาราง สิ่งเดียวกันนี้เกิดขึ้นใน SQL * Server หรือไม่
Mike McAllister

13
MERGE มีความอ่อนไหวต่อสภาพการแข่งขัน (ดูweblogs.sqlteam.com/dang/archive/2009/01/31/31 ) เว้นแต่คุณจะระงับการล็อค นอกจากนี้ลองดูที่ประสิทธิภาพของ MERGE ใน SQL Profiler ... ฉันพบว่ามันช้ากว่าปกติและสร้างการอ่านมากกว่าโซลูชันทางเลือก
EBarr

@EBarr - ขอบคุณสำหรับลิงค์ในล็อค ฉันได้อัปเดตคำตอบของฉันเพื่อรวมคำแนะนำการล็อคแล้ว
Eric Weilnau

นอกจากนี้ตรวจสอบmssqltips.com/sqlservertip/3074/…
Aaron Bertrand

10

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

คำสั่ง Insert + Update ดังกล่าวมักจะเรียกว่าคำสั่ง "Upsert" และสามารถนำมาใช้โดยใช้ MERGE ใน SQL Server

ตัวอย่างที่ดีมากมีให้ที่นี่: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

ข้างต้นอธิบายถึงสถานการณ์การล็อคและการเกิดพร้อมกันเช่นกัน

ฉันจะยกมาเหมือนกันสำหรับการอ้างอิง:

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;

1
มีสิ่งอื่น ๆ ที่คุณต้องกังวลเกี่ยวกับการรวมเป็นmssqltips.com/sqlservertip/3074/…
Aaron Bertrand

8
/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

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

ไชโย


7

คุณสามารถใช้MERGEคำสั่งคำสั่งนี้ใช้ในการแทรกข้อมูลหากไม่มีอยู่หรืออัปเดตหากมีอยู่

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`

@RamenChef ฉันไม่เข้าใจ ข้อใดที่ตรงกับข้อ?
likejudo

@likejudo ฉันไม่ได้เขียนสิ่งนี้ ฉันแค่แก้ไขมัน ถามผู้ใช้ที่เขียนโพสต์
RamenChef

5

หากไปที่การอัพเดท if-no-rows- ปรับปรุงเส้นทาง INSERT ให้ลองทำ INSERT ก่อนเพื่อป้องกันสภาพการแข่งขัน

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET FieldA=@FieldA
   WHERE Key=@Key
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

นอกเหนือจากการหลีกเลี่ยงสภาพการแข่งขันหากในกรณีส่วนใหญ่การบันทึกจะมีอยู่แล้วสิ่งนี้จะทำให้ INSERT ล้มเหลวทำให้เสีย CPU

การใช้ MERGE อาจดีกว่าสำหรับ SQL2008 เป็นต้นไป


แนวคิดที่น่าสนใจ แต่ไวยากรณ์ไม่ถูกต้อง SELECT ต้องการ FROM <table_source> และ TOP 1 (เว้นแต่ table_source ที่เลือกมีเพียง 1 แถว)
jk7

ขอบคุณ ฉันเปลี่ยนเป็น Not EXISTS จะมีแถวที่ตรงกันเพียงหนึ่งแถวเท่านั้นเนื่องจากการทดสอบ "คีย์" ตาม O / P (แม้ว่าอาจจำเป็นต้องใช้คีย์แบบหลายส่วน :))
Kristen

4

ขึ้นอยู่กับรูปแบบการใช้งาน หนึ่งต้องดูการใช้งานภาพใหญ่โดยไม่หลงทางในรายละเอียด ตัวอย่างเช่นหากรูปแบบการใช้งานเป็นอัปเดต 99% หลังจากสร้างเรคคอร์ดแล้ว 'UPSERT' เป็นทางออกที่ดีที่สุด

หลังจากการแทรกครั้งแรก (การเข้าชม) จะเป็นการอัปเดตคำสั่งเดี่ยวทั้งหมดไม่ว่าจะเป็น ifs but buts เงื่อนไข 'ที่ไหน' ในส่วนแทรกมีความจำเป็นมิเช่นนั้นจะเป็นการแทรกรายการที่ซ้ำกันและคุณไม่ต้องการจัดการกับการล็อก

UPDATE <tableName> SET <field>=@field WHERE key=@key;

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END

2

MS SQL Server 2008 แนะนำคำสั่ง MERGE ซึ่งฉันเชื่อว่าเป็นส่วนหนึ่งของมาตรฐาน SQL: 2003 ดังที่หลายคนแสดงให้เห็นว่ามันไม่ใช่เรื่องใหญ่ที่จะจัดการกับกรณีแถวเดียว แต่เมื่อต้องรับมือกับชุดข้อมูลขนาดใหญ่เราต้องการเคอร์เซอร์พร้อมปัญหาประสิทธิภาพทั้งหมดที่เกิดขึ้น คำสั่ง MERGE จะได้รับการต้อนรับอย่างมากเมื่อจัดการกับชุดข้อมูลขนาดใหญ่


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

1

ก่อนที่ทุกคนจะกระโดดไปสู่ความกลัวของ HOLDLOCK จากผู้ใช้ที่มีชื่อเสียงเหล่านี้ที่กำลังใช้งาน sprocs ของคุณโดยตรง :-) ให้ฉันชี้ให้เห็นว่าคุณต้องรับประกันเอกลักษณ์ของ PK-s ใหม่ด้วยการออกแบบ (รหัสประจำตัว ID ภายนอก, แบบสอบถามครอบคลุมโดยดัชนี) นั่นคืออัลฟ่าและโอเมก้าของปัญหา หากคุณไม่มีสิ่งนั้นไม่มี HOLDLOCK-s ของจักรวาลที่จะช่วยคุณและถ้าคุณมีสิ่งนั้นคุณไม่จำเป็นต้องมีอะไรมากไปกว่า UPDLOCK ในการเลือกครั้งแรก (หรือใช้การอัปเดตก่อน)

โดยปกติแล้ว Sprocs จะทำงานภายใต้เงื่อนไขที่ควบคุมได้เป็นอย่างดี หมายความว่าหากรูปแบบการยกระดับแบบง่าย ๆ (อัปเดต + แทรกหรือผสาน) เคยเห็น PK ที่ซ้ำกันซึ่งหมายความว่าบั๊กในการออกแบบระดับกลางหรือตารางของคุณและเป็นเรื่องดีที่ SQL จะตะโกนข้อผิดพลาดในกรณีดังกล่าวและปฏิเสธบันทึก การวาง HOLDLOCK ในกรณีนี้เท่ากับการกินข้อยกเว้นและการรับข้อมูลที่อาจผิดพลาดนอกเหนือจากการลดความสมบูรณ์ของคุณ

ต้องบอกว่าการใช้ MERGE หรือ UPDATE ดังนั้น INSERT นั้นง่ายกว่าบนเซิร์ฟเวอร์ของคุณและเกิดข้อผิดพลาดน้อยลงเนื่องจากคุณไม่จำเป็นต้องเพิ่ม (UPDLOCK) เพื่อเลือกสิ่งแรก นอกจากนี้หากคุณกำลังทำการแทรก / อัพเดทด้วยแบทช์ขนาดเล็กคุณจำเป็นต้องรู้ข้อมูลของคุณเพื่อที่จะตัดสินใจว่าธุรกรรมนั้นเหมาะสมหรือไม่ มันเป็นเพียงการรวบรวมบันทึกที่ไม่เกี่ยวข้องแล้วการทำธุรกรรม "ห่อหุ้ม" เพิ่มเติมจะเป็นอันตราย


1
หากคุณเพียงแค่ทำการอัปเดตจากนั้นแทรกโดยไม่มีการล็อกหรือการแยกระดับสูงผู้ใช้สองคนสามารถพยายามส่งผ่านข้อมูลเดิมกลับมาได้ (ฉันไม่คิดว่ามันจะเป็นข้อผิดพลาดในระดับกลางหากผู้ใช้สองคนพยายามส่งข้อมูลเดียวกันที่ ในเวลาเดียวกัน - ขึ้นอยู่กับบริบทเป็นอย่างมากใช่ไหม) พวกเขาทั้งคู่เข้าสู่การอัปเดตซึ่งส่งกลับ 0 แถวสำหรับทั้งคู่จากนั้นทั้งคู่พยายามแทรก หนึ่งชนะอื่น ๆ ได้รับข้อยกเว้น นี่คือสิ่งที่ผู้คนมักจะพยายามหลีกเลี่ยง
Aaron Bertrand

1

สภาพการแข่งขันมีความสำคัญหรือไม่ถ้าคุณลองอัพเดทตามด้วยการแทรกครั้งแรก? ให้บอกว่าคุณมีสองกระทู้ที่ต้องการตั้งค่าสำหรับคีย์ :

เธรด 1: ค่า = 1
เธรด 2: ค่า = 2

ตัวอย่างสถานการณ์การแข่งขัน

  1. ไม่ได้กำหนดคีย์
  2. เธรด 1 ล้มเหลวด้วยการอัปเดต
  3. เธรด 2 ล้มเหลวด้วยการอัปเดต
  4. หนึ่งในเธรด 1 หรือเธรด 2 หนึ่งสำเร็จด้วยการแทรก เช่นเธรด 1
  5. เธรดอื่นล้มเหลวด้วยการแทรก (พร้อมคีย์ซ้ำที่ผิดพลาด) - เธรด 2

    • ผลลัพธ์: "ครั้งแรก" ของดอกยางสองอันที่จะแทรกตัดสินใจค่า
    • ผลลัพธ์ที่ต้องการ: 2 เธรดสุดท้ายที่จะเขียนข้อมูล (อัพเดตหรือแทรก) ควรเป็นตัวตัดสินมูลค่า

แต่; ในสภาพแวดล้อมแบบมัลติเธรดตัวกำหนดตารางเวลาระบบปฏิบัติการจะตัดสินใจตามลำดับการประมวลผลเธรด - ในสถานการณ์ข้างต้นที่เรามีสภาพการแข่งขันนี้เป็นระบบปฏิบัติการที่ตัดสินใจตามลำดับของการดำเนินการ เช่น: มันผิดที่จะบอกว่า "thread 1" หรือ "thread 2" เป็น "first" จากมุมมองระบบ

เมื่อเวลาของการดำเนินการนั้นใกล้เคียงกับเธรด 1 และเธรด 2 ผลลัพธ์ของสภาวะการแข่งขันไม่สำคัญ ข้อกำหนดเพียงอย่างเดียวควรเป็นว่าหนึ่งในเธรดควรกำหนดค่าผลลัพธ์

สำหรับการนำไปใช้: หากการอัพเดตตามด้วยการแทรกผลลัพธ์ด้วยข้อผิดพลาด "คีย์ซ้ำ" นี่ควรถือว่าเป็นความสำเร็จ

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


1

ใน SQL Server 2008 คุณสามารถใช้คำสั่ง MERGE


11
นี่คือความคิดเห็น ในกรณีที่ไม่มีรหัสตัวอย่างที่แท้จริงนี่ก็เหมือนกับความคิดเห็นอื่น ๆ ในเว็บไซต์
swasheck

เก่ามาก แต่ตัวอย่างก็ดี
Matt McCabe

0

ฉันลองวิธีการแก้ปัญหาด้านล่างและทำงานได้สำหรับฉันเมื่อมีการร้องขอคำสั่งแทรกเกิดขึ้นพร้อมกัน

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert table (key, ...)
   values (@key, ...)
end
commit tran

0

คุณสามารถใช้แบบสอบถามนี้ ทำงานกับ SQL Server ทุกรุ่น มันง่ายและชัดเจน แต่คุณต้องใช้ 2 แบบสอบถาม คุณสามารถใช้หากคุณไม่สามารถใช้ MERGE

    BEGIN TRAN

    UPDATE table
    SET Id = @ID, Description = @Description
    WHERE Id = @Id

    INSERT INTO table(Id, Description)
    SELECT @Id, @Description
    WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)

    COMMIT TRAN

หมายเหตุ: โปรดอธิบายคำตอบเชิงลบ


ฉันคาดเดาว่าไม่มีการล็อค?
Zeek2

ไม่ขาดการล็อค ... ฉันใช้ "TRAN" ธุรกรรม SQL เซิร์ฟเวอร์เริ่มต้นมีการล็อค
Victor Sanchez

-2

หากคุณใช้ ADO.NET DataAdapter จะจัดการสิ่งนี้

หากคุณต้องการจัดการด้วยตัวเองนี่คือวิธี:

ตรวจสอบให้แน่ใจว่ามีข้อ จำกัด คีย์หลักในคอลัมน์คีย์ของคุณ

จากนั้นคุณ:

  1. ทำการอัปเดต
  2. หากการอัปเดตล้มเหลวเนื่องจากมีระเบียนที่มีคีย์อยู่แล้วให้ทำการแทรก หากการอัปเดตไม่ล้มเหลวแสดงว่าคุณดำเนินการเสร็จแล้ว

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


... และการแทรกครั้งแรก (การรู้ว่ามันจะล้มเหลวบางครั้ง) มีราคาแพงสำหรับ SQL Server sqlperformance.com/2012/08/t-sql-queries/error-handling
Aaron Bertrand

-3

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

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')

-3

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

FirstSP:
ถ้ามีอยู่
   โทร SecondSP (UpdateProc)
อื่น
   โทร ThirdSP (InsertProc)

ตอนนี้ฉันไม่ทำตามคำแนะนำของตัวเองบ่อยนักดังนั้นให้เอาเกลือเม็ดหนึ่งไปด้วย


สิ่งนี้อาจเกี่ยวข้องกับ SQL Server รุ่นเก่า แต่รุ่นทันสมัยมีการคอมไพล์ระดับคำสั่ง Forks ฯลฯ ไม่ใช่ปัญหาและการใช้ขั้นตอนแยกต่างหากสำหรับสิ่งเหล่านี้ไม่สามารถแก้ไขปัญหาใด ๆ ที่เกิดขึ้นในการเลือกระหว่างการอัปเดตและการแทรกต่อไป ...
Aaron Bertrand

-10

ทำการเลือกหากคุณได้รับผลลัพธ์ให้อัปเดตถ้าไม่สร้างขึ้นมา


3
นั่นคือการเรียกสองครั้งไปยังฐานข้อมูล
Chris Cudmore

3
ฉันไม่เห็นปัญหากับสิ่งนั้น
Clint Ecker

10
มันคือการเรียกสองครั้งไปยังฐานข้อมูลที่เป็นปัญหาคุณสิ้นสุดจำนวนเสี้ยวรอบเป็นสองเท่าไปยังฐานข้อมูล หากแอพพลิเคชั่นกระทบ db ด้วยการเพิ่ม / แทรกจำนวนมากมันจะทำให้ประสิทธิภาพลดลง UPSERT เป็นกลยุทธ์ที่ดีกว่า
Kev

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