Domanda

Sto lavorando a un'applicazione che dovrebbe creare prodotti (come le polizze assicurative di spedizione) quando si ricevono le notifiche di pagamento istantaneo PayPal. Purtroppo, a volte PayPal invia notifiche duplicate. Inoltre, esiste un'altra terza parte che esegue aggiornamenti del servizio Web contemporaneamente quando ricevono anche aggiornamenti da PayPal.

Ecco un diagramma di base delle tabelle del database coinvolte.

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

Ecco un diagramma di base di ciò che voglio fare:

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;
  }
}

Se ci sono due (o più) thread (o processi o applicazioni) che lo fanno contemporaneamente, voglio che il primo thread blocchi il "pacchetto" riga mentre non ha policyID, fino a quando la policy non viene creata e policyID viene assegnato alla tabella del pacchetto. Quindi il blocco verrebbe rilasciato dopo che policyID è stato assegnato alla tabella dei pacchetti. Spero che l'altro thread che chiama questo stesso codice si interromperà quando legge la riga del pacchetto per assicurarsi che non abbia prima un ID politica. Quando viene rilasciato il blocco della prima transazione, spero che la seconda transazione vedrà il policyID presente e quindi ritorni senza inserire alcuna riga nella tabella delle politiche.

Nota: a causa della progettazione del database CRUD, ciascuna delle procedure memorizzate ha comportato la lettura (selezione), la creazione (inserimento) o l'aggiornamento.

È questo l'uso corretto dell'isolamento delle transazioni RepeatableRead?

Grazie.

È stato utile?

Soluzione

Sarebbe più sicuro e più pulito se inserisca in Policy colpisse solo un vincolo della tabella di unicità nel tentativo di inserire duplicati. Aumentare il livello di isolamento può ridurre la concorrenza e portare ad altre brutte questioni come i deadlock.

Un altro modo è inserire sempre la riga della politica, quindi ripristinarla se il pacchetto è già stato allegato a una politica:

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

Funziona meglio quando i conflitti sono rari, il che sembra essere il tuo caso.

Altri suggerimenti

Credo che tu stia realmente volendo il livello di isolamento serializzabile. Il problema è che due thread possono superare HasInsurancePolicyCheck (anche se non ho idea di cosa farebbe InsurancePolicyDB.Insert o perché restituirebbe 0)

Hai anche molte altre opzioni per questo. Uno sta usando una coda di messaggi ed elabora queste richieste in serie da soli. Un altro è usare sp_getapplock e bloccare alcune chiavi esclusive di quel pacchetto . In questo modo non blocchi più righe o tabelle del necessario.

Accetto la " coda messaggi " idea nella risposta di aaronjensen. Se sei preoccupato per più thread simultanei che tentano di aggiornare contemporaneamente la stessa riga di dati, dovresti invece fare in modo che i thread inseriscano i loro dati in una coda di lavoro, che viene quindi elaborata in sequenza da un singolo thread. Ciò riduce significativamente la contesa sul database, poiché la tabella di destinazione viene aggiornata da un solo thread anziché "N", e le operazioni della coda di lavoro sono limitate agli inserimenti dai thread di messaggistica e alla lettura / aggiornamento dal thread di elaborazione dei dati.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top