문제

다음과 같은 테이블 구조를 가정합니다. MyTable(KEY, datafield1, datafield2...).

종종 기존 레코드를 업데이트하거나, 존재하지 않는 경우 새 레코드를 삽입하고 싶습니다.

기본적으로:

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

이것을 작성하는 가장 좋은 방법은 무엇입니까?

도움이 되었습니까?

해결책

거래를 잊지 마세요.성능은 좋지만 단순한 (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

다른 팁

내 것을 보아라 매우 유사한 이전 질문에 대한 자세한 답변

@보 크로포드의 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 작업이지만 끔찍한 코드입니다. :-(

UPSERT를 수행합니다.

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   INSERT INTO MyTable (FieldA) VALUES (@FieldA)

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

많은 사람들이 사용을 제안할 것입니다. 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에서 포착하도록 허용하는 것 외에 다른 이유가 없다면 이는 문제가 됩니다.나는 여기서 많은 것을 증명합니다:

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

편집하다:

아쉽게도 선택 없이 이 작업을 수행하는 솔루션이 한 단계 더 적은 단계로 작업을 수행하기 때문에 더 나은 것 같다는 점을 인정해야 합니다.

한 번에 두 개 이상의 레코드를 UPSERT하려는 경우 ANSI SQL:2003 DML 문 MERGE를 사용할 수 있습니다.

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의 MERGE 문 모방.

이에 대해 논평하기에는 꽤 늦었지만 MERGE를 사용하여 더 완전한 예제를 추가하고 싶습니다.

이러한 Insert+Update 문은 일반적으로 "Upsert" 문이라고 하며 SQL Server의 MERGE를 사용하여 구현할 수 있습니다.

여기에 아주 좋은 예가 나와 있습니다.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;
/*
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 라인의 변수에 대해 적절한 값(및 유형)을 설정하십시오.

건배.

당신이 사용할 수있는 MERGE 명령문, 이 명령문은 존재하지 않는 경우 데이터를 삽입하거나 존재하는 경우 업데이트하는 데 사용됩니다.

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

UPDATE if-no-rows-updated 이후 INSERT 경로로 가는 경우 경쟁 조건을 방지하기 위해 먼저 INSERT를 수행하는 것을 고려하십시오(중간 DELETE가 없다고 가정).

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가 낭비됩니다.

SQL2008 이후에는 MERGE를 사용하는 것이 바람직할 것입니다.

SQL Server 2008에서는 MERGE 문을 사용할 수 있습니다.

사용 패턴에 따라 다릅니다.세부사항에 빠져들지 않고 사용법의 큰 그림을 보아야 합니다.예를 들어, 레코드가 생성된 후 사용 패턴이 99% 업데이트인 경우 'UPSERT'가 가장 좋은 솔루션입니다.

첫 번째 삽입(적중) 후에는 if나 but이 아닌 모든 단일 명령문 업데이트가 됩니다.삽입 시 'where' 조건이 필요합니다. 그렇지 않으면 중복 항목이 삽입되므로 잠금을 처리하고 싶지 않습니다.

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

MS SQL Server 2008에는 SQL:2003 표준의 일부인 MERGE 문이 도입되었습니다.많은 사람들이 보여주듯이 한 행의 사례를 처리하는 것은 큰 문제가 아니지만 대규모 데이터 세트를 처리할 때는 커서가 필요하며 그에 따른 모든 성능 문제가 있습니다.대규모 데이터 세트를 처리할 때 MERGE 문을 추가하면 매우 환영받을 것입니다.

모두가 귀하의 sproc을 직접 실행하는 사악한 사용자의 두려움 때문에 HOLDLOCK-s로 점프하기 전에 다음을 지적하겠습니다. 설계에 따라 새로운 PK의 고유성을 보장해야 합니다. (ID 키, Oracle의 시퀀스 생성기, 외부 ID에 대한 고유 인덱스, 인덱스로 처리되는 쿼리).그것이 문제의 알파이자 오메가이다.그것이 없으면 우주의 어떤 HOLDLOCK도 당신을 구할 수 없으며, 만약 당신이 그것을 가지고 있다면 첫 번째 선택에서(또는 업데이트를 먼저 사용하기 위해) UPDLOCK 이외의 것은 필요하지 않습니다.

Sprocs는 일반적으로 매우 통제된 조건에서 신뢰할 수 있는 호출자(중간 계층)를 가정하여 실행됩니다.즉, 간단한 upsert 패턴(업데이트+삽입 또는 병합)에서 중복 PK가 발견되면 이는 중간 계층 또는 테이블 디자인에 버그가 있음을 의미하며 그러한 경우 SQL이 오류를 표시하고 레코드를 거부하는 것이 좋습니다.이 경우 HOLDLOCK을 배치하는 것은 성능을 저하시키는 것 외에도 예외를 먹고 잠재적으로 잘못된 데이터를 가져오는 것과 같습니다.

즉, MERGE 또는 UPDATE를 사용한 다음 INSERT를 사용하는 것이 서버에서 더 쉽고 오류 발생 가능성도 적습니다. 먼저 선택하기 위해 (UPDLOCK)을 추가하는 것을 기억할 필요가 없기 때문입니다.또한 작은 배치로 삽입/업데이트를 수행하는 경우 트랜잭션이 적절한지 여부를 결정하려면 데이터를 알아야 합니다.이는 관련되지 않은 레코드의 모음일 뿐이므로 추가 "봉투" 트랜잭션이 해로울 수 있습니다.

먼저 업데이트를 시도한 후 삽입을 시도하는 경우 경쟁 조건이 실제로 중요합니까?키 값을 설정하려는 두 개의 스레드가 있다고 가정해 보겠습니다. 열쇠:

스레드 1:값 = 1
스레드 2:값 = 2

경쟁 조건 시나리오 예

  1. 열쇠 정의되지 않았습니다
  2. 스레드 1이 업데이트에 실패함
  3. 스레드 2가 업데이트에 실패함
  4. 스레드 1이나 스레드 2 중 정확히 하나가 삽입에 성공합니다.예:스레드 1
  5. 다른 스레드는 삽입(오류 중복 키 포함)으로 실패합니다. - 스레드 2.

    • 결과:삽입할 두 트레드 중 "첫 번째"가 값을 결정합니다.
    • 원하는 결과:데이터를 쓰는 2개의 스레드 중 마지막(업데이트 또는 삽입)이 값을 결정해야 합니다.

하지만;멀티스레드 환경에서는 OS 스케줄러가 스레드 실행 순서를 결정합니다. 위의 시나리오에서 경쟁 조건이 있는 경우 실행 순서를 결정하는 것은 OS였습니다.즉:시스템 관점에서 "스레드 1" 또는 "스레드 2"가 "첫 번째"라고 말하는 것은 잘못된 것입니다.

스레드 1과 스레드 2의 실행 시간이 너무 가까우면 경쟁 조건의 결과는 중요하지 않습니다.유일한 요구 사항은 스레드 중 하나가 결과 값을 정의해야 한다는 것입니다.

구현을 위해:업데이트 후 삽입으로 인해 "중복 키" 오류가 발생하면 성공으로 처리되어야 합니다.

또한, 물론 데이터베이스의 값이 마지막에 작성한 값과 동일하다고 가정해서는 안 됩니다.

아래 솔루션을 시도했는데 삽입 문에 대한 동시 요청이 발생할 때 저에게 효과적입니다.

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

이 쿼리를 사용할 수 있습니다.모든 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

메모:부정적인 답변을 설명해주세요

ADO.NET을 사용하는 경우 DataAdapter가 이를 처리합니다.

직접 처리하고 싶다면 다음과 같은 방법을 사용하세요.

키 열에 기본 키 제약 조건이 있는지 확인하세요.

그러면 당신은:

  1. 업데이트를 하세요
  2. 해당 키가 있는 레코드가 이미 존재하여 업데이트가 실패하는 경우 삽입을 수행하십시오.업데이트가 실패하지 않으면 완료된 것입니다.

다른 방법으로도 할 수 있습니다.먼저 삽입을 수행하고 삽입이 실패하면 업데이트를 수행합니다.업데이트가 삽입보다 더 자주 수행되기 때문에 일반적으로 첫 번째 방법이 더 좋습니다.

존재하는 경우 수행 중 ...또 다른 ...최소 2개의 요청(확인용 1개, 조치용 1개)을 수행해야 합니다.다음 접근 방식에서는 레코드가 있는 경우 하나만 필요하고 삽입이 필요한 경우 두 개가 필요합니다.

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')

나는 일반적으로 먼저 존재하는지 확인한 다음 올바른 경로가 무엇이든 수행하는 것과 관련하여 다른 여러 포스터가 말한 대로 수행합니다.이 작업을 수행할 때 기억해야 할 한 가지는 SQL에 의해 캐시된 실행 계획이 한 경로 또는 다른 경로에 대해 최적이 아닐 수 있다는 것입니다.이를 수행하는 가장 좋은 방법은 두 가지 다른 저장 프로시저를 호출하는 것입니다.

FirstSP:
If Exists
   Call SecondSP (UpdateProc)
Else
   Call ThirdSP (InsertProc)

이제 나는 내 조언을 자주 따르지 않으므로 소금 한 알씩 받아들이십시오.

선택을 수행하고, 결과가 나오면 업데이트하고, 그렇지 않으면 생성하세요.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top