2009-01-30 13 views
34

Me gustaría actualizar un conjunto de filas basadas en un criterio simple y obtener la lista de PK que se cambiaron. Pensé que sólo podía hacer algo como esto, pero estoy preocupado por posibles problemas de concurrencia:¿Hay alguna manera de SELECCIONAR y ACTUALIZAR filas al mismo tiempo?

SELECT Id FROM Table1 WHERE AlertDate IS NULL; 
UPDATE Table1 SET AlertDate = getutcdate() WHERE AlertDate IS NULL; 

Si que se envuelve en una transacción ¿hay problemas de concurrencia que pueden ocurrir? ¿O hay una mejor manera de hacer esto?

Respuesta

65

Considere la posibilidad de mirar el OUTPUT clause capability of UPDATE (también DELETE y INSERT). Ejemplo de la página vinculada MSDN:

UPDATE TOP (10) HumanResources.Employee 
SET VacationHours = VacationHours * 1.25, 
    ModifiedDate = GETDATE() 
OUTPUT inserted.BusinessEntityID, 
     deleted.VacationHours, 
     inserted.VacationHours, 
     inserted.ModifiedDate 
INTO @MyTableVar; 
+0

Se aplica a 'ACTUALIZAR CONJUNTO ... FROM ... WHERE ....'? – Kiquenet

8

Sería más fácil hacer su ACTUALIZACIÓN primero y luego ejecutar 'SELECCIONAR ID DE INSERTADO'.

Eche un vistazo a SQL Tips para obtener más información y ejemplos.

0

si es dentro de la transacción, el sistema de bloqueo de base de datos se hará cargo de los problemas de concurrencia. por supuesto, si usa uno (el valor predeterminado de mssql es que usa bloqueo, entonces establece si no anula eso)

0

Editar: mi problema es que quería que la selección muestre los resultados después de la actualización, no la actualice de un seleccionar.

¿Ha intentado con una selección secundaria?

update mytable set mydate = sysdate 
where mydate in (select mydate from mytable where mydate is null); 
12

Una manera de manejar esto es hacerlo de una transacción, y hacer su consulta SELECT tomar un bloqueo de actualización en las filas seleccionadas hasta que se complete la transacción.

BEGIN TRAN 

SELECT Id FROM Table1 WITH (UPDLOCK) 
WHERE AlertDate IS NULL; 

UPDATE Table1 SET AlertDate = getutcdate() 
WHERE AlertDate IS NULL; 

COMMIT TRAN 

Esto elimina la posibilidad de que un cliente concurrentes actualiza las filas seleccionadas en el momento entre su SELECT y actualización.

Al confirmar la transacción, se liberarán los bloqueos de actualización.

Otra forma de manejar esto es declarar un cursor para su SELECCIONAR con la opción FOR UPDATE. Luego ACTUALIZA DONDE ACTUAL DEL CURSOR. Lo siguiente no está probado, pero debería darle la idea básica:

DECLARE cur1 CURSOR FOR 
    SELECT AlertDate FROM Table1 
    WHERE AlertDate IS NULL 
    FOR UPDATE; 

DECLARE @UpdateTime DATETIME 

SET @UpdateTime = GETUTCDATE() 

OPEN cur1; 

FETCH NEXT FROM cur1; 

WHILE @@FETCH_STATUS = 0 
BEGIN 

    UPDATE Table1 AlertDate = @UpdateTime 
    WHERE CURRENT OF cur1; 

    FETCH NEXT FROM cur1; 

END 
+3

+1 para UPDLOCK, es una solución correcta a este problema y en transacciones cortas no dará lugar a interbloqueos –

+0

¿Esto dará como resultado que todas las filas obtengan el mismo valor de fecha y hora (como lo haría en una sola llamada de SQL)? De lo contrario, debe obtener el tiempo antes del ciclo en una variable y simplemente establecer AltertDate a la variable. Si es un problema, edítelo en. – Thorsten

+1

+1 para UPDLOCK, y -1 para CURSOR. – jsuddsjr

3

Quizás algo más como esto?

declare @UpdateTime datetime 

set @UpdateTime = getutcdate() 

update Table1 set AlertDate = @UpdateTime where AlertDate is null 

select ID from Table1 where AlertDate = @UpdateTime 
+1

Esto tiene la agradable ventaja de que, incluso si no haces esto dentro de una transacción, será * muy * difícil que obtengas resultados alterados en algún otro proceso. –

0

en SQL 2008 una nueva declaración TSQL se introduce "MERGE" que realiza insertar, actualizar o eliminar las operaciones en una tabla de destino en base a los resultados de una combinación con una tabla de origen. Puede sincronizar dos tablas insertando, actualizando o eliminando filas en una tabla según las diferencias encontradas en la otra tabla.

http://blogs.msdn.com/ajaiman/archive/2008/06/25/tsql-merge-statement-sql-2008.aspx http://msdn.microsoft.com/en-us/library/bb510625.aspx

6

Muchos años después ...

La respuesta aceptada de la utilización de la cláusula OUTPUT es buena.Tuve que desenterrar la sintaxis real, así que aquí está:

DECLARE @UpdatedIDs table (ID int) 
UPDATE 
    Table1 
SET 
    AlertDate = getutcdate() 
OUTPUT 
    inserted.Id 
INTO 
    @UpdatedIDs 
WHERE 
    AlertDate IS NULL; 

AÑADIDO-SEP-14, 2015: "? ¿Puedo usar una variable escalar en lugar de una variable de tabla"

uno puede preguntar ... Lo siento, pero no, no puedes. Tendrá que SELECT @SomeID = ID from @UpdatedIDs si necesita una única identificación.

1

Me he enfrentado al mismo problema; Tengo que actualizar el monto del crédito, y tengo que obtener el tiempo modificado, junto con los detalles de crédito de DB. Se trata básicamente de

síncrono/atómicamente realizo (ACTUALIZACIÓN A continuación, obtener) en MySQL

He intentado muchas opciones y encontró uno que resolvió mi problema.

1) OPCION_1 SELECT FOR UPDATE

Este es el mantenimiento de la cerradura hasta que la actualización (SYNC de GET ACTUALIZAR), pero necesito bloquear después de la actualización hasta que el GET.

2) OPTION_2 Procedimiento almacenado

procedimiento almacenado no se ejecutará de forma sincrónica como lua Redis, por lo que también necesitamos código de sincronización para realizar eso.

3) option_3 Transacción

he utilizado APP entityManager como a continuación, pensaron que antes de cometer nadie puede actualizar, y antes de cometer voy a conseguir el objeto actualizado junto con la hora de modificación (de DB). Pero no recibí el último objeto. Solo comprometo tengo lo último.

try { 
     entityManager.getTransaction().begin(); 
     //entityManager.persist(object); 
     int upsert = entityManager.createNativeQuery(
     "update com.bill.Credit c set c.balance = c.balance - ?1 
      where c.accountId = ?2 and c.balance >= ?1").executeUpdate(); 
      //c.balance >= ? for limit check 
     Credit newCredit = entityManager.find(Credit.class, "id"); 
     entityManager.refresh(newCredit); //SHOULD GET LATEST BUT NOT 
     entityManager.getTransaction().commit(); 
    } finally {  
     entityManager.unwrap(Session.class).close(); 
    } 

4) OPTION_4 LOCK resolvió el problema, por lo que antes de la actualización, adquirí el bloqueo; luego después de OBTENER, he liberado el bloqueo.

private Object getLock(final EntityManager entityManager, final String Id){ 

    entityManager.getTransaction().begin(); 
    Object obj_acquire = entityManager.createNativeQuery("SELECT GET_LOCK('" + Id + "', 10)").getSingleResult(); 
    entityManager.getTransaction().commit(); 
    return obj_acquire; 
} 


private Object releaseLock(final EntityManager entityManager, final String Id){ 

    entityManager.getTransaction().begin(); 
    Object obj_release = entityManager.createNativeQuery("SELECT RELEASE_LOCK('" + Id + "')").getSingleResult(); 
    entityManager.getTransaction().commit(); 
    return obj_release; 
} 
+0

*** MySQL ***? La pregunta está etiquetada para 'SQL SERVER' – Kiquenet

+0

OHH MY BAD, Answer is for MySQL –

Cuestiones relacionadas