Pregunta

Estoy trabajando en una aplicación que se supone crea productos (como pólizas de seguro de envío) cuando se reciben notificaciones de pago instantáneo de PayPal.Lamentablemente, PayPal a veces envía notificaciones duplicadas.Además, hay otro tercero que también realiza actualizaciones del servicio web simultáneamente cuando recibe actualizaciones de PayPal.

A continuación se muestra un diagrama básico de las tablas de la base de datos involucradas.

// table "package"
// columns packageID, policyID, other data...
// 
// table "insurancepolicy"
// columns policyID, coverageAmount, other data...

Aquí hay un diagrama básico de lo que quiero hacer:

using (SqlConnection conn = new SqlConnection(...))
{
  sqlTransaction sqlTrans = conn.BeginTransaction(IsolationLevel.RepeatableRead);

  // Calls a stored procedure that checks if the foreign key in the transaction table has a value.
  if (PackageDB.HasInsurancePolicy(packageID, conn))
  { 
    sqlTrans.Commit();
    return false;
  }

  // Insert row in foreign table.
  int policyID = InsurancePolicyDB.Insert(coverageAmount, conn);
  if (policyID <= 0)
  {
    sqlTrans.Rollback();
    return false;
  }

  // Assign foreign key to parent table.  If this fails, roll back everything.
  bool assigned = PackageDB.AssignPolicyID(packageID, policyID, conn);
  if (!assigned)
  {
    sqlTrans.Rollback();
    return false;
  }
}

Si hay dos (o más) subprocesos (o procesos o aplicaciones) haciendo esto al mismo tiempo, quiero que el primer subproceso bloquee la fila del "paquete" mientras no tenga un ID de política, hasta que se cree la política y se asigne el ID de política. a la tabla de paquetes.Luego, el bloqueo se liberará después de que se asigne el ID de política a la tabla de paquetes.Tengo la esperanza de que el otro hilo que llama a este mismo código se detenga cuando lea la fila del paquete para asegurarse de que no tenga un ID de política primero.Cuando se libere el bloqueo de la primera transacción, tengo la esperanza de que la segunda transacción vea que el ID de política está allí y, por lo tanto, regrese sin insertar ninguna fila en la tabla de políticas.

Nota:Debido al diseño de la base de datos CRUD, cada procedimiento almacenado implicaba Leer (seleccionar), Crear (insertar) o Actualizar.

¿Es este el uso correcto del aislamiento de transacciones RepetibleRead?

Gracias.

¿Fue útil?

Solución

Sería más seguro y limpio si insert into Policy simplemente presione alguna restricción de la tabla de unicidad al intentar insertar un duplicado.Aumentar el nivel de aislamiento puede reducir la concurrencia y provocar otros problemas desagradables, como interbloqueos.

Otra forma es insertar siempre la fila de Política y luego revertirla si el Paquete ya se ha adjuntado a una Política:

begin tran (read committed)

/* tentatively insert new Policy */
insert Policy

/* attach Package to Policy if it's still free */
update Package
  set Package.policy_id = @policy_id
  where Package.package_id = @package_id and Package.policy_id is null

if @@rowcount > 0
  commit
else
  rollback

Esto funciona mejor cuando los conflictos son poco frecuentes, que parece ser su caso.

Otros consejos

Creo que en realidad quieres un nivel de aislamiento serializable.El problema es que dos subprocesos pueden pasar HasInsurancePolicyCheck (aunque no tengo idea de qué haría InsurancePolicyDB.Insert o por qué devolvería 0)

También tienes muchas otras opciones para esto.Una es utilizar una cola de mensajes y procesar estas solicitudes en serie usted mismo.Otra es usar sp_getapplock y bloquear alguna clave exclusiva de ese paquete.De esa manera no bloqueará más filas o tablas de las necesarias.

Estoy de acuerdo con la idea de la "cola de mensajes" en la respuesta de Aaronjensen.Si le preocupa que varios subprocesos simultáneos intenten actualizar la misma fila de datos simultáneamente, debería hacer que los subprocesos inserten sus datos en una cola de trabajo, que luego es procesada secuencialmente por un solo subproceso.Esto reduce significativamente la contención en la base de datos, porque la tabla de destino se actualiza mediante un solo subproceso en lugar de "N", y las operaciones de la cola de trabajo se limitan a inserciones por parte de los subprocesos de mensajería y una lectura/actualización por parte del subproceso de procesamiento de datos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top