2010-06-07 15 views
7

Esta pregunta ciertamente se aplica a un alcance mucho más amplio, pero aquí está.Forma correcta de generar números de orden en SQL Server

Tengo una aplicación de comercio electrónico básica, donde los usuarios pueden, naturalmente, hacer pedidos. Dichos pedidos deben tener un número único, que estoy tratando de generar en este momento.

Cada pedido es específico del vendedor. Básicamente, tengo una tabla OrderNumberInfo (VendorID, OrderNumber). Ahora, cada vez que un cliente realiza un pedido, necesito incrementar OrderNumber para un Vendedor en particular y devolver ese valor. Naturalmente, no quiero otros procesos para interferir conmigo, así que necesito para bloquear exclusivamente esta fila de alguna manera:

begin tranaction 

    declare @n int 
    select @n = OrderNumber 
     from OrderNumberInfo 
     where VendorID = @vendorID 

    update OrderNumberInfo 
     set OrderNumber = @n + 1 
     where OrderNumber = @n and VendorID = @vendorID 

commit transaction 

Ahora, que he leído sobre select ... with (updlock rowlock), el bloqueo pesimista, etc., pero simplemente no puede ajustarse todo esto en una imagen coherente:

  • ¿Cómo funcionan estas sugerencias con el aislamiento de instantáneas de SQL Server 2008?
  • ¿Realizan bloqueos a nivel de fila, nivel de página o incluso nivel de tabla?
  • ¿Cómo se tolera esto que varios usuarios intenten generar números para un único proveedor?
  • ¿Qué niveles de aislamiento son apropiados aquí?
  • Y en general, ¿cuál es la manera de hacer tales cosas?

EDITAR

sólo para hacer más claro algunas cosas:

  • rendimiento en este rincón particular de la aplicación no es en absoluto un problema: los pedidos se colocan con poca frecuencia y supondrá una llamada costosa al servicio web de los vendedores, por lo que un retraso de 1 segundo es bastante tolerable
  • Nosotros realmente necesitamos tener los números de orden de cada proveedor para b e independiente y secuencial

Respuesta

3

Puede utilizar una cláusula OUTPUT. Esto debería hacerlo de manera atómica sin requerir una transacción.

-- either return the order number directly as a single column resultset 
UPDATE OrderNumberInfo 
SET OrderNumber = OrderNumber + 1 
    OUTPUT DELETED.OrderNumber 
WHERE VendorID = @vendorID 


-- or use an intermediate table variable to get the order number into @n 
DECLARE @n INT 
DECLARE @temp TABLE (OrderNumber INT) 

UPDATE OrderNumberInfo 
SET OrderNumber = OrderNumber + 1 
    OUTPUT DELETED.OrderNumber 
    INTO @temp (OrderNumber) 
WHERE VendorID = @vendorID 

SET @n = (SELECT TOP 1 OrderNumber FROM @temp) 

Los ejemplos anteriores asumir que la columna de la VendorID tiene una restricción único, o por lo menos que sólo va a ser una fila por cada ID de proveedor. Si ese no es el caso, posiblemente estará actualizando y/o devolviendo varias filas, ¡lo que no parece una buena idea!

+0

¡Eso es interesante! ¡Gracias! –

+0

Si la columna OrderNumber representa el último OrderNumber utilizado, ¿no le gustaría Inserted.OrderNumber? – Thomas

+0

@Thomas: Solo estoy imitando el comportamiento del código proporcionado en la pregunta, que primero obtiene el número de pedido y luego incrementa la columna. Pero si desea incrementar y luego obtener el número de orden incrementado, entonces 'INSERTED.OrderNumber' sería la manera de hacerlo. – LukeH

4

Su solución creará un posible cuello de botella de rendimiento en la tabla OrderNumberInfo.

¿Hay algún motivo específico por el que los pedidos no puedan ser simplemente una columna de identidad, posiblemente con el prefijo de un proveedor en el lado de la aplicación (por ejemplo, MSFT-232323)?

El único inconveniente de este enfoque es que las órdenes por proveedor no serán un patrón "Add-1-to-get-next-order-#", pero no conozco ninguna consideración técnica o comercial de por qué eso presentaría un problema, aunque podría hacer un procesamiento de orden en secuencia un poco más complicado.

Todavía se incrementarán y serán únicos por proveedor, que es el único requisito real para una ID de pedido.

Obtendrá, por supuesto, el beneficio adicional de una lógica muy fácil de proveedor independiente suponiendo que alguna vez tenga alguna), como el control de calidad de toda la aplicación/informes.

+0

Desafortunadamente, este es el requisito: cada proveedor necesita tener una secuencia independiente. –

+2

@Anton - bien ... disculpa por ser denso, pero ¿cuál es la razón de origen/negocio para este requisito? – DVK

+2

De acuerdo con DVK, está haciendo algo totalmente innecesario y arriesgado. Si tuviera esto como un requerimiento, los rechazaría y les diría que el riesgo supera cualquier beneficio percibido. Mientras que cada proveedor tenga pedidos únicos, todo está bien, no es como si fueran a ver los ID de los demás y, literalmente, no existe un motivo comercial válido por el que los pedidos tengan que estar en orden secuencial sin saltarse ningún valor. El costo adicional de hacer esto y el riesgo adicional de problemas de integridad de datos si no lo hace bien la primera vez son mucho más altos que el uso de un campo de identidad, para literalmente ningún beneficio. Ninguna. – HLGEM

1

normalmente uso algo como esto:

update OrderNumberInfo with (rowlock) 
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1 
where VendorID = @VendorID 

No tiene por que ser envuelto en una transacción. De hecho, si lo envuelve en una transacción, entonces SQL Server comenzará a mantener bloqueos en la tabla y ralentizará todo. Cuando necesito hacer cosas como esta en un servicio web, siempre lo ejecuto en una conexión de base de datos separada fuera de cualquier transacción que pueda estar abierta en ese momento, solo para estar seguro.

Creo (pero no lo he probado) que SQL Server usa un pestillo en lugar de una transacción para hacerlo atómico, lo que debería ser más eficiente.

Si el diseño de la tabla es tal que la fila proveedor tiene que ser creado en la demanda si no existe, a continuación, utilizar esta lógica en su lugar:

declare @error int, @rowcount int 

-- Attempt to read and update the number. 
update OrderNumberInfo with (rowlock) 
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1 
where VendorID = @VendorID 

select @error = @@error, @rowcount = @@rowcount 
if @error <> 0 begin 
    return @error 
end 

-- If the update succeeded then exit now. 
if @rowcount > 0 begin 
    return 0 
end 

-- Insert the row if it doesn't exist yet. 
insert into OrderNumberInfo (VendorID, OrderNumber) 
select VendorID, 1 
where not exists (select null from OrderNumberInfo where VendorID = @VendorID) 

select @error = @@error 
if @error <> 0 begin 
    return @error 
end 

-- Attempt to read and update the number. 
update OrderNumberInfo with (rowlock) 
set @OrderNumber = OrderNumber, OrderNumber = OrderNumber + 1 
where VendorID = @VendorID 

select @error = @@error 
if @error <> 0 begin 
    return @error 
end 

Este código todavía no requiere una transacción, porque cada declaración atómica tendrá éxito independientemente de cuántas otras conexiones estén ejecutando el código simultáneamente.

Descargo de responsabilidad: Lo he usado sin problemas en SQL Server 7-2005. Yo aún no puedo comentar sobre su comportamiento en 2008.

0

La manera de hacer esto para mantener la consistencia:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 
BEGIN TRANSACTION 
declare @n int 
select @n = OrderNumber 
    from OrderNumberInfo 
    where VendorID = @vendorID 

update OrderNumberInfo 
    set OrderNumber = @n + 1 
    where OrderNumber = @n and VendorID = @vendorID 

COMMIT TRANSACTION 

Esto utilizará la forma más estricto de aislamiento y se asegurará nada gracioso.

0

aquí está:

declarar @C int = 0; update Table set Código = @ C, @ C = @ C + 1

Cuestiones relacionadas