Méthode C # pour verrouiller la table SQL Server
J'ai un programme C# qui doit effectuer un groupe de mises à jour en masse (20k) dans une table SQL Server. Depuis que les autres utilisateurs peuvent mettre à jour ces enregistrements un à un via un site intranet, nous avons besoin de construire le programme C# avec la fonctionnalité de verrouillage de la table vers le bas. Une fois que la table est verrouillée pour empêcher un autre utilisateur d'y apporter des modifications/recherches on aura alors besoin de préformation de la demande de mises à jour/inserts.
Puisque nous sommes le traitement de nombreux dossiers, nous ne pouvons pas utiliser TransactionScope
(semblait le moyen le plus facile au premier abord) due au fait que notre transaction vents est gérée par le Service MSDTC. Nous avons besoin d'utiliser une autre méthode.
Fondée sur ce que j'ai lu sur l'internet à l'aide d'un SqlTransaction
objet semblait être la meilleure méthode, mais je ne peux pas obtenir le tableau de verrouillage. Lorsque le programme s'exécute et j'ai pas le code ci-dessous, je suis encore capable de faire des mises à jour et de la recherche via le site intranet.
Ma question est double. Suis-je à l'aide de la SqlTransaction
correctement? Si oui (ou même si) il y a une meilleure méthode pour l'obtention d'un verrou de table qui permet au programme en cours d'exécution de la recherche et de la préformation des mises à jour?
Je voudrais pour la table pour être verrouillé alors que le programme exécute le code ci-dessous.
C#
SqlConnection dbConnection = new SqlConnection(dbConn);
dbConnection.Open();
using (SqlTransaction transaction = dbConnection.BeginTransaction(IsolationLevel.Serializable))
{
//Instantiate validation object with zip and channel values
_allRecords = GetRecords();
validation = new Validation();
validation.SetLists(_allRecords);
while (_reader.Read())
{
try
{
record = new ZipCodeTerritory();
_errorMsg = string.Empty;
//Convert row to ZipCodeTerritory type
record.ChannelCode = _reader[0].ToString();
record.DrmTerrDesc = _reader[1].ToString();
record.IndDistrnId = _reader[2].ToString();
record.StateCode = _reader[3].ToString().Trim();
record.ZipCode = _reader[4].ToString().Trim();
record.LastUpdateId = _reader[7].ToString();
record.ErrorCodes = _reader[8].ToString();
record.Status = _reader[9].ToString();
record.LastUpdateDate = DateTime.Now;
//Handle DateTime types separetly
DateTime value = new DateTime();
if (DateTime.TryParse(_reader[5].ToString(), out value))
{
record.EndDate = Convert.ToDateTime(_reader[5].ToString());
}
else
{
_errorMsg += "Invalid End Date; ";
}
if (DateTime.TryParse(_reader[6].ToString(), out value))
{
record.EffectiveDate = Convert.ToDateTime(_reader[6].ToString());
}
else
{
_errorMsg += "Invalid Effective Date; ";
}
//Do not process if we're missing LastUpdateId
if (string.IsNullOrEmpty(record.LastUpdateId))
{
_errorMsg += "Missing last update Id; ";
}
//Make sure primary key is valid
if (_reader[10] != DBNull.Value)
{
int id = 0;
if (int.TryParse(_reader[10].ToString(), out id))
{
record.Id = id;
}
else
{
_errorMsg += "Invalid Id; ";
}
}
//Validate business rules if data is properly formatted
if (string.IsNullOrWhiteSpace(_errorMsg))
{
_errorMsg = validation.ValidateZipCode(record);
}
//Skip record if any errors found
if (!string.IsNullOrWhiteSpace(_errorMsg))
{
_issues++;
//Convert to ZipCodeError type in case we have data/formatting errors
_errors.Add(new ZipCodeError(_reader), _errorMsg);
continue;
}
else if (flag)
{
//Separate updates to appropriate list
SendToUpdates(record);
}
}
catch (Exception ex)
{
_errors.Add(new ZipCodeError(_reader), "Job crashed reading this record, please review all columns.");
_issues++;
}
}//End while
//Updates occur in one of three methods below. If I step through the code,
//and stop the program here, before I enter any of the methods, and then
//make updates to the same records via our intranet site the changes
//made on the site go through. No table locking has occured at this point.
if (flag)
{
if (_insertList.Count > 0)
{
Updates.Insert(_insertList, _errors);
}
if (_updateList.Count > 0)
{
_updates = Updates.Update(_updateList, _errors);
_issues += _updateList.Count - _updates;
}
if (_autotermList.Count > 0)
{
//_autotermed = Updates.Update(_autotermList, _errors);
_autotermed = Updates.UpdateWithReporting(_autotermList, _errors);
_issues += _autotermList.Count - _autotermed;
}
}
transaction.Commit();
}
source d'informationauteur NealR
Vous devez vous connecter pour publier un commentaire.
SQL n'a pas vraiment d'offrir un moyen de verrouiller exclusivement une table: il est conçu pour essayer de maximiser l'utilisation simultanée tout en gardant à l'ACIDE.
Vous pourriez essayer à l'aide de ces indicateurs de table sur vos requêtes:
TABLOCK
TABLOCKX
UPDLOCK
XLOCK
HOLDLOCK/SERIALIZABLE
Sinon, vous pouvez essayer de DÉFINIR NIVEAU d'ISOLATION de TRANSACTION SÉRIALISABLE:
Mais presque certainement, l'escalade de verrous cause de blocage et de vos utilisateurs seront assez mort dans l'eau (dans mon expérience).
Donc...
Attendre jusqu'à ce que vous avez un calendrier d'entretien de la fenêtre. Définir la base de données en mode mono-utilisateur, effectuez vos modifications, puis de le remettre en ligne.
Essayez ceci: lorsque vous recevez des enregistrements à partir de la vous table (dans la GetRecords() la fonction?) utiliser l'indicateur TABLOCKX:
Il mettra en file d'attente tous les autres lectures et des mises à jour à l'extérieur de votre transaction jusqu'à ce que la transaction soit commis ou annulée.
Il est tout au sujet de niveau d'Isolement ici. Changer votre Niveau d'Isolation de Transaction à ReadCommited (n'a pas à la recherche de la Valeur d'Enum en C#, mais qui devrait être proche). Lorsque vous exécutez la première mise à jour/insertion de la table, SQL va commencer verrouillage et personne ne sera en mesure de lire les données que vous êtes la modification, l'ajout jusqu'à ce que vous Commit ou Rollback thr de la transaction, à condition qu'ils ne sont pas d'effectuer des lectures incorrectes (à l'aide de NoLock sur leurs SQL, ou d'avoir la connexion de niveau d'Isolement de la valeur Read Uncommited).. attention tout de même, selon la façon dont vous êtes l'insertion, la mise à jour des données vous pouvez verrouiller l'ensemble de la table pour la durée de votre transaction, mais qui pourrait causer des erreurs de délai d'attente chez le client quand ils essaient de lire à partir de ce tableau, tandis que votre transaction est ouverte. Sans voir le SQL derrière les mises à jour mais je ne peux pas dire si ça va se produire ici.
Comme quelqu'un l'a souligné, l'opération ne semble pas être utilisé après être sortis.
À partir du peu d'informations que nous avons sur l'application et le but, c'est difficile à dire, mais à partir de l'extrait de code, il me semble que nous n'avons pas besoin de verrouillage. Nous sommes certains de données à partir de la source X (dans ce cas _reader) et ensuite, l'insertion, la mise à jour à destination Y.
Tous la validation se produit à l'encontre de la source de données pour s'assurer qu'il est correct, il ne semble pas que nous sommes de prendre toute décision ou de soins pour ce qui est de la destination.
Si ce qui précède est vrai puis une meilleure approche consisterait à charger toutes ces données dans une table temporaire (peut être un véritable table temp "#" ou une "vraie" table que nous détruire par la suite, mais le but est le même), et puis, en une seule instruction sql, nous pouvons faire une masse d'insertion/mise à jour de la table temporaire dans notre destination. En supposant que la db schéma est dans une forme décente, 20 (ou 30) en milliers de dossiers qui devrait arriver presque instantanément, sans avoir à attendre que la fenêtre de maintenance ou de lock-out utilisateurs pour de longues périodes de temps
Aussi strictement répondre à la question sur l'aide de la transaction, ci-dessous est un exemple simple sur la façon d'utiliser correctement une transaction, il devrait y avoir beaucoup d'autres échantillons et d'information sur le web