“미리보기 모드”가있는 데이터베이스 저장 프로 시저


15

내가 작업하는 데이터베이스 응용 프로그램에서 상당히 일반적인 패턴은 "미리보기 모드"가있는 보고서 또는 유틸리티에 대해 저장 프로 시저를 만들어야한다는 것입니다. 이러한 프로 시저가 업데이트를 수행 할 때이 매개 변수는 조치 결과가 리턴되어야하지만 실제로 프로시 저는 데이터베이스에 대한 업데이트를 수행하지 않아야 함을 나타냅니다.

이를 수행하는 한 가지 방법 if은 매개 변수에 대한 명령문을 작성하고 두 개의 완전한 코드 블록을 갖는 것입니다. 하나는 데이터를 업데이트하고 반환하고 다른 하나는 데이터를 반환합니다. 그러나 이것은 코드 복제와 미리보기 데이터가 실제로 업데이트로 발생하는 상황을 정확하게 반영한다는 비교적 낮은 신뢰도 때문에 바람직하지 않습니다.

다음 예제는 트랜잭션 세이브 포인트 및 변수 (임시 테이블과 달리 트랜잭션의 영향을받지 않음)를 사용하여 미리보기 모드에 대해 단일 코드 블록 만 라이브 업데이트 모드로 사용하려고합니다.

참고 : 이 프로 시저 호출 자체가 트랜잭션에 중첩 될 수 있으므로 트랜잭션 롤백 은 옵션이 아닙니다. 이것은 SQL Server 2012에서 테스트되었습니다.

CREATE TABLE dbo.user_table (a int);
GO

CREATE PROCEDURE [dbo].[PREVIEW_EXAMPLE] (
  @preview char(1) = 'Y'
) AS

CREATE TABLE #dataset_to_return (a int);

BEGIN TRANSACTION; -- preview mode required infrastructure
  DECLARE @output_to_return TABLE (a int);
  SAVE TRANSACTION savepoint;

  -- do stuff here
  INSERT INTO dbo.user_table (a)
    OUTPUT inserted.a INTO @output_to_return (a)
    VALUES (42);

  -- catch preview mode
  IF @preview = 'Y'
    ROLLBACK TRANSACTION savepoint;

  -- save output to temp table if used for return data
  INSERT INTO #dataset_to_return (a)
  SELECT a FROM @output_to_return;
COMMIT TRANSACTION;

SELECT a AS proc_return_data FROM #dataset_to_return;
RETURN 0;
GO

-- Examples
EXEC dbo.PREVIEW_EXAMPLE @preview = 'Y';
SELECT a AS user_table_after_preview_mode FROM user_table;

EXEC dbo.PREVIEW_EXAMPLE @preview = 'N';
SELECT a AS user_table_after_live_mode FROM user_table;

-- Cleanup
DROP TABLE dbo.user_table;
DROP PROCEDURE dbo.PREVIEW_EXAMPLE;
GO

이 코드 및 디자인 패턴에 대한 피드백을 찾고 있거나 동일한 문제에 대한 다른 솔루션이 다른 형식으로 존재하는지 여부를 찾고 있습니다.

답변:


12

이 방법에는 몇 가지 결함이 있습니다.

  1. "미리보기"라는 용어는 운영되는 데이터의 특성 (및 운영에서 운영으로 변경되는 데이터)에 따라 대부분의 경우 상당히 오해의 소지가 있습니다. "미리보기"데이터가 수집 된 시간과 15 분 후 사용자가 커피를 마시고 담배를 피우기 위해 밖으로 나가기 시작한 후 다시 돌아 오는 시점 사이에 현재 작동중인 데이터가 동일한 상태가되도록하는 것 블록 주위로 돌아와서 이베이에서 무언가를 확인하고 실제로 작업을 수행하기 위해 "확인"버튼을 클릭하지 않았다는 것을 알고 최종적으로 버튼을 클릭합니까?

    미리보기가 생성 된 후 작업을 진행하는 데 시간 제한이 있습니까? 또는 데이터가 초기 SELECT시간 과 수정 시간에 동일한 상태에 있는지 확인하는 방법 일까요?

  2. 예제 코드가 서둘러 수행 될 수 있고 실제 사용 사례를 나타내지는 않았지만 이는 왜 INSERT조작에 대한 "미리보기"가 있을까요? 그것은 같은 것을 통해 여러 행을 삽입 할 때 의미가 있고 삽입 가능한 행 INSERT...SELECT수는 다양 할 수 있지만 싱글 톤 연산에는 그다지 의미가 없습니다.

  3. 미리보기 데이터가 실제로 업데이트로 발생하는 상황을 정확하게 반영한다는 비교적 낮은 신뢰도 때문에 이는 바람직하지 않습니다.

    이 "낮은 신뢰도"는 정확히 어디에서 오는가? SELECT여러 테이블이 조인되고 결과 집합에 행이 중복 될 때 표시되는 것보다 다른 수의 행을 업데이트 할 수 있지만 여기서는 문제가되지 않습니다. 의 영향을 받아야하는 행은 UPDATE자체적으로 선택할 수 있습니다. 불일치가 있으면 쿼리를 잘못 수행 한 것입니다.

    업데이트 될 테이블의 여러 행과 일치하는 JOINed 테이블로 인해 중복이 발생하는 상황은 "미리보기"가 생성되는 상황이 아닙니다. 그리고 이런 경우가있는 경우 누군가에게만 오류가 발생하지 않도록 보고서 내에서 반복되는 보고서의 하위 세트를 업데이트하도록 사용자에게 설명해야합니다. 영향을받는 행 수를보고.

  4. 완전성을 위해 (다른 답변이 이것을 언급했지만) TRY...CATCH구성을 사용하지 않으므로 이러한 호출을 중첩 할 때 (저장 점을 사용하지 않거나 트랜잭션을 사용하지 않더라도) 문제가 발생할 수 있습니다. 중첩 된 저장 프로 시저 호출에서 트랜잭션을 처리하는 템플릿에 대한 다음 질문 (여기서는 DBA.SE)에 대한 답변을 참조하십시오.

    C # 코드 및 저장 프로 시저에서 트랜잭션을 처리해야합니까?

  5. 하더라도 문제는 상기 회계 처리 언급 중요한 결함이 여전히 존재한다 : 시간의 짧은 기간 동안의 동작 (즉, 이전에 수행되고 ROLLBACK(쿼리를 사용하여) 임의의 더티 읽기 질의 WITH (NOLOCK)또는 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED데이터를 잡을 수있는) 그 잠시 후가 아닙니다. 더러운 읽기 쿼리를 사용하는 모든 사용자가 이미 알고 있어야 및 가능성, 이와 같은 작업이 크게있는 데이터 이상을 소개하는 기회를 늘리는 것이 가능했지만 매우 디버깅하기 어려운 (의미 : 당신이하려고 보내고 싶어 얼마나 많은 시간을 직접적인 원인이없는 문제를 찾으십니까?).

  6. 이와 같은 패턴은 또한 더 많은 잠금을 수행하여 차단을 늘리고 더 많은 트랜잭션 로그 활동을 생성하여 시스템 성능을 저하시킵니다. (지금 @MartinSmith도이 두 가지 문제를 질문에 대한 의견으로 언급했습니다.)

    또한 수정중인 테이블에 트리거가있는 경우 불필요한 추가 처리 (CPU 및 물리적 / 논리적 읽기)가 될 수 있습니다. 트리거는 또한 더티 읽기로 인해 데이터 이상이 발생할 가능성을 더욱 증가시킵니다.

  7. 위에서 언급 한 사항 (잠금 증가)과 관련하여 트랜잭션을 사용하면 특히 트리거가 관련된 경우 교착 상태에 빠질 가능성이 높아집니다.

  8. "작은 INSERT미리보기"데이터는 DEFAULT제약 조건 ( Sequences/ NEWID()/ NEWSEQUENTIALID()) 및으로 결정된 열 값과 관련하여 삽입 된 것과 동일하지 않을 수 있습니다 IDENTITY.

  9. 테이블 변수의 내용을 임시 테이블에 쓰는 데 추가 오버 헤드가 필요하지 않습니다. 이것은 ROLLBACK테이블 변수의 데이터에 영향을 미치지 않으므로 (처음에 테이블 변수를 사용한다고 말한 이유는) SELECT FROM @output_to_return;끝 부분에 더 의미가 있고 임시 만들기를 귀찮게하지 않습니다. 표.

  10. 이 Save Points의 뉘앙스를 알 수없는 경우 (단 하나의 Stored Procedure 만 표시하므로 예제 코드에서는 알기가 어렵습니다.) 작업이 예상대로 작동 하도록 고유 한 Save Point 이름 을 사용해야 ROLLBACK {save_point_name}합니다. 이름을 다시 사용하는 경우 ROLLBACK은 해당 이름의 가장 최근 저장 점을 롤백합니다.이 이름 ROLLBACK은에서 호출되는 곳과 동일한 중첩 수준에 있지 않을 수 있습니다 . 이 동작의 실제 동작을 보려면 다음 답변의 첫 번째 예제 코드 블록을 참조하십시오 . 저장 프로 시저의 트랜잭션

이것이 내려 오는 것은 :

  • "미리보기"를하는 것은 사용자를 향한 작업에는 의미가 없습니다. 유지 관리 작업을 위해이 작업을 자주 수행하여 작업을 진행할 때 삭제 / 가비지 수집 내용을 확인할 수 있습니다. 라는 선택적 매개 변수를 추가 하고을 수행 할 때 @TestMode수행하는 IF명령문을 수행합니다 . 때로는 응용 프로그램에서 호출 한 저장 프로 시저에 매개 변수를 추가하여 데이터 상태에 영향을주지 않으면 서 간단한 테스트를 수행 할 수 있지만이 매개 변수는 응용 프로그램에서 사용되지 않습니다.SELECT@TestMode = 1DELETE@TestMode

  • "문제"의 상단 섹션에서 이것이 명확하지 않은 경우 :

    "미리보기"/ "테스트"모드가 필요 / 원하는 경우 DML 문을 실행할 경우 어떤 영향을 받는지 확인하려면 트랜잭션 (예 : BEGIN TRAN...ROLLBACK패턴)을 사용하지 마십시오 . 기껏해야 단일 사용자 시스템에서만 작동하는 패턴이며 해당 상황에서는 좋은 아이디어조차 아닙니다.

  • IF명령문 의 두 분기간에 대량의 쿼리를 반복하면 변경 될 때마다 두 분기를 모두 업데이트해야하는 잠재적 인 문제가 있습니다. 그러나 두 쿼리의 차이점은 일반적으로 코드 검토에서 파악하기 쉽고 수정하기 쉽습니다. 반면, 상태 차이 및 더티 읽기와 같은 문제는 찾아서 수정하기가 훨씬 어렵습니다. 그리고 시스템 성능 저하 문제는 해결이 불가능합니다. 우리는 SQL이 객체 지향 언어가 아니라는 것을 인식하고 수용해야하며, 중복 된 코드를 캡슐화 / 축소하는 것은 다른 많은 언어와 마찬가지로 SQL의 디자인 목표가 아닙니다.

    쿼리가 길거나 복잡하면 인라인 테이블 반환 함수로 쿼리를 캡슐화 할 수 있습니다. 그런 다음 SELECT * FROM dbo.MyTVF(params);"미리보기"모드를 간단하게 수행하고 "do it"모드의 키 값에 참여할 수 있습니다. 예를 들면 다음과 같습니다.

    UPDATE tab
    SET    tab.Col2 = tvf.ColB
           ...
    FROM   dbo.Table tab
    INNER JOIN dbo.MyTVF(params) tvf
            ON tvf.ColA = tab.Col1;
  • 언급 한 보고서 시나리오 인 경우 초기 보고서를 실행하는 것이 "미리보기"입니다. 누군가 보고서에 표시되는 내용 (아마도 상태)을 변경하려면 현재 표시된 데이터를 변경해야하므로 추가 미리보기가 필요하지 않습니다.

    오퍼레이션이 특정 % 또는 비즈니스 규칙으로 입찰 금액을 변경하는 경우 프리젠 테이션 계층 (JavaScript?)에서 처리 할 수 ​​있습니다.

  • 최종 사용자 대면 작업에 대해 "미리보기"를 수행해야하는 경우 먼저 데이터의 상태를 캡처해야합니다 ( UPDATE작업 에 대한 결과 집합의 모든 필드 해시 또는 키 값). DELETE그런 다음 작업을 수행하기 전에 캡처 된 상태 정보를 현재 정보와 비교하십시오- 테이블 에서 잠금을 수행하는 트랜잭션 내HOLD 에서이 비교를 수행 한 후에도 아무것도 변경되지 않도록하십시오. 차이가 있으면 또는으로 ROLLBACK진행 하는 대신 오류를 수행하십시오 .UPDATEDELETE

    UPDATE연산의 차이점을 감지하기 위해 관련 필드에서 해시를 계산하는 대안은 ROWVERSION 유형의 열을 추가하는 입니다. ROWVERSION데이터 유형 의 값은 해당 행이 변경 될 때마다 자동으로 변경됩니다. 이러한 열이 SELECT있는 경우 다른 "미리보기"데이터와 함께 열을 입력 한 다음 키 값 및 값과 함께 "확인, 계속 진행하여 업데이트"단계로 전달하십시오. 바꾸다. 그런 다음 ROWVERSION"미리보기"에서 전달 된 값을 현재 값 (각 키당)과 비교하고 UPDATEif ALL 로만 진행합니다.일치하는 값 중 여기서의 장점은, 가능성이 적더라도, 부정 부정 가능성이있는 해시를 계산할 필요가 없으며을 수행 할 때 마다 약간의 시간이 걸린다 는 것 SELECT입니다. 반면에 ROWVERSION값은 변경 될 때만 자동으로 증가하므로 걱정할 필요가 없습니다. 그러나 ROWVERSION유형은 8 바이트이므로 많은 테이블 및 / 또는 많은 행을 처리 할 때 더해질 수 있습니다.

    UPDATE작업 과 관련하여 일관성이없는 상태를 감지하는 두 가지 방법에는 각각 장단점이 있으므로 시스템의 "con"보다 "pro"가 더 높은 방법을 결정해야합니다. 그러나 두 경우 모두 미리보기 생성과 작업 수행 사이의 지연이 최종 사용자의 기대를 벗어난 동작을 유발하지 않도록 할 수 있습니다.

  • 최종 사용자를 대상으로하는 "미리보기"모드를 수행하는 경우 선택시 레코드 상태 캡처, 전달 및 수정시 검사 이외에DATETIME for SelectTime및 채우기 GETDATE()또는 이와 유사한 것을 포함하십시오. 스토어드 프로 시저에서 확인할 수 있도록 스토어드 프로 시저 (대부분 단일 입력 매개 변수로)로 다시 전달 될 수 있도록이를 앱 계층으로 전달하십시오. 그런 다음 작업이 "미리보기"모드가 아닌 경우, 다음 것을 확인할 수 @SelectTime값이 요구 될 보다 더 의 현재 값 이전에 X 분 GETDATE(). 아마 2 분? 5 분? 대부분 10 분을 넘지 않아야합니다. DATEDIFFMINUTES가 해당 임계 값을 초과 하면 오류가 발생합니다 .


4

가장 간단한 접근 방식이 가장 좋으며 특히 동일한 모듈이 아닌 SQL의 코드 복제와 관련하여 많은 문제가 없습니다. 두 쿼리가 모두 다른 일을 한 후에. 따라서 'Route 1'또는 Keep It Simple을 사용 하지 말고 저장된 proc에 두 개의 섹션이 있습니다. 하나는 수행해야 할 작업을 시뮬레이트하고 하나는 수행해야 할 작업을 시뮬레이트합니다.

CREATE TABLE dbo.user_table ( rowId INT IDENTITY PRIMARY KEY, a INT NOT NULL, someGuid UNIQUEIDENTIFIER DEFAULT NEWID() );
GO
CREATE PROCEDURE [dbo].[PREVIEW_EXAMPLE2]

    @preview CHAR(1) = 'Y'

AS

    SET NOCOUNT ON

    --!!TODO add error handling

    IF @preview = 'Y'

        -- Simulate INSERT; could be more complex
        SELECT 
            ISNULL( ( SELECT MAX(rowId) FROM dbo.user_table ), 0 ) + 1 AS rowId,
            42 AS a,
            NEWID() AS someGuid

    ELSE

        -- Actually do the INSERT, return inserted values
        INSERT INTO dbo.user_table ( a )
        OUTPUT inserted.rowId, inserted.a, inserted.someGuid
        VALUES ( 42 )

    RETURN

GO

이는 자체 문서화 (즉, IF ... ELSE따르기 쉬움), 복잡성이 낮고 (테이블 변수 접근 방식 IMO를 사용하여 저장 점과 비교하여) 버그가 적다는 장점이 있습니다 (@Cody에서 큰 반점).

낮은 신뢰도에 대한 귀하의 요점에 대해서는 잘 모르겠습니다. 논리적으로 동일한 기준을 가진 두 개의 쿼리는 동일한 작업을 수행해야합니다. UPDATE과 (와) 사이에 카디널리티 불일치가 SELECT있을 수 있지만 조인 및 기준의 기능이됩니다. 더 설명해 주시겠습니까?

따로, NULL/ NOT NULL속성과 테이블 및 테이블 변수를 설정하고 기본 키 설정을 고려하십시오.

/ / 작업이 일반보다 높은 잠금 수준을 요구 하기 때문에 원래의 접근 방식은 약간 복잡해져 교착 상태가 발생하기 쉽습니다 .INSERTUPDATEDELETESELECTs

나는 당신의 실제 procs가 더 복잡하다고 생각합니다. 따라서 위의 접근 방식이 효과가 없다고 생각하면 더 많은 예제를 게시하십시오.


3

나의 관심사는 다음과 같습니다.

  • 트랜잭션 처리는 Begin Try / Begin Catch 블록에 중첩되는 표준 패턴을 따르지 않습니다. 이것이 템플리트 인 경우 몇 가지 단계를 더 포함하여 스토어드 프로 시저에서 데이터를 여전히 수정 한 상태로 미리보기 모드에서이 트랜잭션을 종료 할 수 있습니다.

  • 형식을 따르면 개발자 작업이 증가합니다. 내부 열을 변경하면 테이블 변수 정의도 수정 한 다음 임시 테이블 정의를 수정 한 다음 끝에 삽입 열을 수정해야합니다. 인기가 없을 것입니다.

  • 일부 저장 프로시 저는 매번 같은 형식의 데이터를 반환하지 않습니다. sp_WhoIsActive를 일반적인 예로 생각하십시오.

나는 그것을하는 더 좋은 방법을 제공하지는 않았지만 당신이 가지고있는 것이 좋은 패턴이라고 생각하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.