Question

Je travaille sur une application censée créer des produits (comme des polices d'assurance pour l'expédition) lorsque les notifications de paiement instantané PayPal sont reçues. Malheureusement, PayPal envoie parfois des notifications en double. En outre, un autre tiers effectue simultanément des mises à jour de service Web lorsqu'il reçoit des mises à jour de PayPal.

Voici un schéma de base des tables de la base de données impliquées.

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

Voici un schéma de base de ce que je veux faire:

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

S'il y a deux (ou plus) threads (ou processus ou applications) qui le font en même temps, je veux que le premier thread verrouille le "package". ligne tant qu’il n’a pas de politique id, jusqu’à ce que la politique soit créée et que le policyID soit affecté à la table de packages. Ensuite, le verrou serait libéré une fois que l'ID de stratégie est affecté à la table de packages. J'espère que l'autre thread qui appelle ce même code se mettra en pause lorsqu'il lira la ligne du paquet pour s'assurer qu'il ne possède pas de code de stratégie en premier. Lorsque le verrou de la première transaction est libéré, j'espère que la deuxième transaction verra que l'ID de politique est présent et qu'elle sera donc renvoyée sans insérer de lignes dans la table des politiques.

Remarque: en raison de la conception de la base de données CRUD, chacune des procédures stockées impliquées était lue (lecture), créée (insérée) ou mise à jour.

Est-ce la bonne utilisation de l'isolation de transaction RepeatableRead?

Merci.

Était-ce utile?

La solution

Il serait plus sûr et plus propre si insérer dans la stratégie suffit de frapper une contrainte de table d'unicité lors de la tentative d'insertion de doublons. L'augmentation du niveau d'isolement peut réduire la concurrence et entraîner d'autres problèmes épineux tels que des impasses.

Vous pouvez également toujours insérer une ligne de stratégie, puis l'annuler si un package a déjà été associé à une stratégie:

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

Cela fonctionne mieux lorsque les conflits sont rares, ce qui semble être votre cas.

Autres conseils

Je crois que vous voulez réellement un niveau d’isolation sérialisable. Le problème est que deux threads peuvent aller au-delà de HasInsurancePolicyCheck (bien que je n’aie aucune idée de ce que InsurancePolicyDB.Insert ferait ni du résultat 0)

Vous avez également de nombreuses autres options pour cela. L'une utilise une file d'attente de messages et traite ces demandes en série vous-même. Une autre consiste à utiliser sp_getapplock et à verrouiller une clé unique de ce package. . De cette manière, vous ne verrouillez pas plus de lignes ou de tableaux que nécessaire.

Je suis d'accord avec la "file de messages". idée dans la réponse de aaronjensen. Si plusieurs threads simultanés tentent de mettre à jour simultanément la même ligne de données vous inquiètent, vous devez au contraire les faire insérer dans une file d'attente de travail, qui est ensuite traitée de manière séquentielle par un seul thread. Cela réduit considérablement les conflits sur la base de données, car la table cible est mise à jour par un seul thread au lieu de "N" et les opérations de la file d'attente de travail sont limitées aux insertions par les threads de messagerie et à une lecture / mise à jour par le thread de traitement de données.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top