Pregunta

Tengo una tabla [Archivo] que tiene el siguiente 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]

La idea es que el FileID se use como la clave de la tabla y el Nombre es la ruta completa que representa un archivo.

Lo que he estado tratando de hacer es crear un Procedimiento almacenado que verificará si el Nombre ya está en uso, de ser así, use ese registro o cree un nuevo registro.

Pero cuando hago una prueba de esfuerzo del código con muchos hilos que ejecutan el procedimiento almacenado a la vez, obtengo diferentes errores.

Esta versión del código creará un punto muerto y generará una excepción de punto muerto en el 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 versión del código termino obteniendo filas con los mismos datos en la columna Nombre.

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

Me pregunto cuál es la forma correcta de hacer este tipo de acción. En general, este es un patrón que me gustaría usar donde los datos de la columna son únicos en una sola columna o en varias columnas y otra columna se usa como clave.

Gracias

¿Fue útil?

Solución

Si está buscando mucho en el campo Nombre, probablemente quiera indexarlo (como único, y tal vez incluso agrupado si este es el campo de búsqueda principal ). Como no utiliza el @FileID de la primera selección, simplemente seleccionaría count (*) del archivo donde Name = @Name y vería si es mayor que cero (esto evitará que SQL retenga cualquier bloqueo en la tabla de la fase de búsqueda, ya que no hay columnas seleccionadas).

Está en el curso correcto con el nivel SERIALIZABLE, ya que su acción afectará el éxito o el fracaso de las consultas posteriores con el Nombre presente. La razón por la que la versión sin ese conjunto causa duplicados es que dos selecciones se ejecutaron simultáneamente y encontraron que no había ningún registro, por lo que ambos continuaron con las inserciones (lo que crea el duplicado).

El punto muerto con la versión anterior probablemente se deba a la falta de un índice que hace que el proceso de búsqueda tarde mucho tiempo. Cuando cargue el servidor en una transacción SERIALIZABLE, todo lo demás tendrá que esperar a que se complete la operación. El índice debería acelerar la operación, pero solo las pruebas indicarán si es lo suficientemente rápido. Tenga en cuenta que puede responder a la transacción fallida volviendo a enviarla: en situaciones del mundo real, con suerte, la carga será transitoria.

EDITAR: Al indexar su tabla, pero sin usar SERIALIZABLE, termina con tres casos:

  • Se encuentra el nombre, se captura y se usa la ID. Común
  • El nombre no se encuentra, se inserta como se esperaba. Común
  • No se encuentra el nombre, la inserción falla porque se publicó otra coincidencia exacta dentro de los milisegundos del primero. Muy raro

Esperaría que este último caso sea verdaderamente excepcional, por lo que sería preferible utilizar una excepción para capturar este caso muy raro que comprometer SERIALIZABLE, lo que tiene serias consecuencias de rendimiento.

Si realmente espera que sea común tener publicaciones dentro de milisegundos entre sí con el mismo nuevo nombre, utilice una transacción SERIALIZABLE junto con el índice. Será más lento en el caso general, pero más rápido cuando se encuentren estas publicaciones.

Otros consejos

Primero, cree un índice único en la columna Nombre. Luego, desde su código de cliente, primero verifique si el Nombre existe seleccionando el ID de archivo y colocando el Nombre en la cláusula where; si es así, use el ID de archivo. Si no, inserte uno nuevo.

El uso de la función Existe puede limpiar un poco las cosas.

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top