Tengo una tabla con números secuenciales (piense en números de factura o ID de estudiante).Acceso concurrente a la base de datos: evitando que dos usuarios obtengan el mismo valor
En algún momento, el usuario necesita solicitar el número anterior (para calcular el siguiente número). Una vez que el usuario conoce el número actual, necesita generar el siguiente número y agregarlo a la tabla.
Mi preocupación es que dos usuarios puedan generar erróneamente dos números idénticos debido al acceso concurrente.
He oído hablar de procedimientos almacenados, y sé que esa podría ser una solución. ¿Hay una mejor práctica aquí para evitar problemas de concurrencia?
Editar: Esto es lo que tengo hasta ahora:
USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_GetNextOrderNumber]
AS
BEGIN
BEGIN TRAN
DECLARE @recentYear INT
DECLARE @recentMonth INT
DECLARE @recentSequenceNum INT
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- get the most recent numbers
SELECT @recentYear = Year, @recentMonth = Month, @recentSequenceNum = OrderSequenceNumber
FROM dbo.OrderNumbers
WITH (XLOCK)
WHERE Id = (SELECT MAX(Id) FROM dbo.OrderNumbers)
// increment the numbers
IF (YEAR(getDate()) > IsNull(@recentYear,0))
BEGIN
SET @recentYear = YEAR(getDate());
SET @recentMonth = MONTH(getDate());
SET @recentSequenceNum = 0;
END
ELSE
BEGIN
IF (MONTH(getDate()) > IsNull(@recentMonth,0))
BEGIN
SET @recentMonth = MONTH(getDate());
SET @recentSequenceNum = 0;
END
ELSE
SET @recentSequenceNum = @recentSequenceNum + 1;
END
-- insert the new numbers as a new record
INSERT INTO dbo.OrderNumbers(Year, Month, OrderSequenceNumber)
VALUES (@recentYear, @recentMonth, @recentSequenceNum)
COMMIT TRAN
END
Esto parece funcionar, y me da los valores que quiero. Hasta ahora, aún no he agregado ningún bloqueo para evitar el acceso concurrente.
Editar 2: Se agregó WITH(XLOCK)
para bloquear la tabla hasta que finalice la transacción. No voy a tener un rendimiento aquí. Siempre y cuando no se agreguen entradas duplicadas y no se produzcan bloqueos, esto debería funcionar.
El número que generaré consiste en el año (3 dígitos), seguido del mes (2 dígitos), seguido de un número de 3 dígitos que se restablece a cero cada mes. Así que algo así como 012 03 000. –
dependiendo de dónde provenga ese número de 3 dígitos, puede resolverlo fácilmente con una columna calculada. De lo contrario, utilice un procedimiento – Diego
El número de 3 dígitos simplemente se incrementa desde el número anterior (a menos que el número anterior sea del mes anterior, luego el número es 0 nuevamente) –