Qual é o padrão correto para os dados única em colunas?
-
19-08-2019 - |
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
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