0

次のスキーマを持つテーブル [ファイル] があります

CREATE TABLE [dbo].[File]
(
    [FileID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](256) NOT NULL,
 CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED 
(
    [FileID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

FileID がテーブルのキーとして使用され、Name がファイルを表す完全修飾パスであるという考え方です。

私がやろうとしているのは、名前が既に使用されているかどうかを確認するストアド プロシージャを作成することです。その場合はそのレコードを使用し、そうでない場合は新しいレコードを作成します。

しかし、ストアド プロシージャを一度に実行する多くのスレッドでコードのストレス テストを行うと、さまざまなエラーが発生します。

このバージョンのコードは、デッドロックを作成し、クライアントでデッドロック例外をスローします。

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION xact_File_Create
    SET XACT_ABORT ON

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

このバージョンのコードでは、名前列に同じデータを含む行が取得されます。

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    BEGIN TRANSACTION xact_File_Create

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

このタイプのアクションを実行する正しい方法は何ですか?一般に、これは、列データが単一の列または複数の列で一意であり、別の列がキーとして使用される場合に使用したいパターンです。

ありがとう

4

3 に答える 3

1

名前フィールドを頻繁に検索する場合は、おそらくインデックスを作成する必要があります (一意であり、これがプライマリ検索フィールドである場合はクラスター化することもできます)。最初の選択で @FileID を使用しないため、Name = @Name のファイルから count(*) を選択し、それが 0 より大きいかどうかを確認します (これにより、SQL がテーブルのロックを保持できなくなります)。列が選択されていないため、検索フェーズ)。

SERIALIZABLE レベルで正しい方向に進んでいます。これは、名前が存在する場合、アクションが後続のクエリの成功または失敗に影響を与えるためです。そのセットのないバージョンで重複が発生する理由は、2 つの選択が同時に実行され、レコードがないことが判明したため、両方が挿入を続行したためです (重複が作成されます)。

以前のバージョンでのデッドロックは、検索プロセスに時間がかかるインデックスがないことが原因である可能性が最も高いです。SERIALIZABLE トランザクションでサーバーをロードすると、他のすべては操作が完了するまで待機する必要があります。インデックス操作を高速にする必要がありますが、十分に高速かどうかはテストのみで示されます。再送信することで、失敗したトランザクションに応答できることに注意してください。実際の状況では、負荷が一時的なものになることを願っています。

EDIT:SERIALIZABLEを使用せずにテーブルをインデックス化すると、次の3つのケースになります。

  • 名前が見つかり、ID が取得されて使用されます。一般
  • 名前が見つかりません。期待どおりに挿入されます。一般
  • 名前が見つかりません。最初のミリ秒以内に別の正確な一致が投稿されたため、挿入は失敗しました。激レア

この最後のケースは本当に例外的であると予想されるため、パフォーマンスに深刻な影響を与える SERIALIZABLE を使用するよりも、例外を使用してこの非常にまれなケースをキャプチャすることをお勧めします。

同じ新しい名前の投稿が数ミリ秒以内に投稿されることが一般的であることが本当に予想される場合は、インデックスと組み合わせて SERIALIZABLE トランザクションを使用します。一般的なケースでは遅くなりますが、これらの投稿が見つかった場合は速くなります。

于 2009-01-20T22:53:03.060 に答える
1

まず、Name 列に一意のインデックスを作成します。次に、クライアント コードから、まず FileID を選択し、名前を where 句に入力して、名前が存在するかどうかを確認します。存在する場合は、FileID を使用します。そうでない場合は、新しいものを挿入してください。

于 2009-01-20T22:45:29.567 に答える
0

Exists 関数を使用すると、少しきれいになるかもしれません。

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
于 2009-01-20T22:44:31.923 に答える