Pergunta

Eu tenho uma tabela [File] que tem o seguinte esquema

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]

A idéia é que o FileID é usado como a chave para a tabela e o nome é o caminho totalmente qualificado que representa um arquivo.

O que eu tenho tentado fazer é criar um procedimento armazenado que irá verificar para ver se o nome já está em uso em caso afirmativo, em seguida, usar esse registro pessoa criar um novo registro.

Mas quando eu forço testar o código com muitas threads em execução o procedimento armazenado em uma vez que recebo erros diferentes.

Esta versão do código irá criar um impasse e lançar uma exceção impasse no cliente.

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

Esta versão do código que eu acabar ficando linhas com os mesmos dados na coluna Nome.

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

Eu estou querendo saber o que o caminho certo para fazer este tipo de ação é? Em geral, este é um teste padrão que pretende utilizar, onde os dados da coluna é único em qualquer uma única coluna ou várias colunas e outra coluna é utilizado como a chave.

Graças

Foi útil?

Solução

Se você estiver procurando pesadamente no campo Nome, você provavelmente quer que ele indexados (como único, e talvez até mesmo agrupados, se este é o campo principal procurar). Como você não use o @FileID da primeira escolha, gostaria apenas de seleccionar count (*) from arquivo onde Name = @Name e ver se ele é maior do que zero (isso vai impedir SQL de manter os bloqueios na mesa de a fase de pesquisa, como há colunas são selecionadas).

Você está no caminho certo com o nível SERIALIZABLE, como sua ação vai impactar consultas subsequentes sucesso ou fracasso com o nome estar presente. A razão a versão sem esse conjunto faz com que as duplicatas é que dois seleciona funcionou simultaneamente e descobri que não havia nenhum registro, assim que ambos foram em frente com as inserções (que cria a duplicata).

O impasse com a versão anterior é mais provável devido à falta de um índice tornando o processo de busca levar um longo tempo. Quando você carrega o servidor para baixo em uma transação SERIALIZABLE, tudo o resto vai ter que esperar para que a operação completa. O índice deve fazer a operação rápida, mas apenas testando vai indicar se é rápido o suficiente. Note que você pode responder à transação falhou, reenviando: em situações reais espero que a carga vai ser transitória.

EDIT: Ao fazer sua mesa indexada, mas não usando SERIALIZABLE, você acaba com três casos:

  • Nome for encontrado, ID é capturado e usado. Common
  • Nome não for encontrado, inserções como esperado. Common
  • Nome não for encontrado, inserção falha porque outra correspondência exata foi publicado em milésimos de segundos do primeiro. Muito raros

Espero que este último caso de ser verdadeiramente excepcional, portanto, usando uma exceção para capturar este caso muito raro seria preferível SERIALIZABLE envolvente, o que tem consequências graves de desempenho.

Se você realmente tem uma expectativa de que será comum ter mensagens em milésimos de segundos um do outro do mesmo new nome, em seguida, usar uma transação SERIALIZABLE em conjunto com o índice. Vai ser mais lento no caso geral, mas mais rápido quando essas mensagens são encontrados.

Outras dicas

Primeiro, crie um índice exclusivo na coluna Nome. Em seguida, a partir do seu código de cliente primeiro cheque se o nome existe, selecionando o FileID e colocar o nome na cláusula onde - se isso acontecer, use o FileID. Se não, coloque uma nova.

Usando o Exists função poderá coisas limpar um pouco.

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top