Zde je můj názor na problém:
-
Při použití více vláken pro vkládání/aktualizaci/dotazování dat na SQL Server nebo jakoukoli databázi jsou uváznutí běžnou skutečností. Musíte předpokládat, že k nim dojde, a náležitě s nimi zacházet.
-
To neznamená, že bychom se neměli pokoušet omezit výskyt uváznutí. Je však snadné přečíst si základní příčiny zablokování a podniknout kroky, jak jim předejít, ale SQL Server vás vždy překvapí :-)
Nějaký důvod zablokování:
-
Příliš mnoho vláken – snažte se omezit počet vláken na minimum, ale samozřejmě chceme více vláken pro maximální výkon.
-
Nedostatek indexů. Pokud nejsou výběry a aktualizace dostatečně selektivní, SQL odstraní zámky většího rozsahu, než je zdravé. Zkuste zadat vhodné indexy.
-
Příliš mnoho indexů. Aktualizace indexů způsobuje uváznutí, takže se snažte snížit indexy na požadované minimum.
-
Úroveň izolace transakce je příliš vysoká. Výchozí úroveň izolace při použití .NET je 'Serializable', zatímco výchozí úroveň při použití serveru SQL je 'Read Committed'. Snížení úrovně izolace může hodně pomoci (pokud je to samozřejmě vhodné).
Váš problém bych mohl vyřešit takto:
-
Neroloval bych vlastní řešení vláken, použil bych knihovnu TaskParallel. Moje hlavní metoda by vypadala asi takto:
using (var dc = new TestDataContext()) { // Get all the ids of interest. // I assume you mark successfully updated rows in some way // in the update transaction. List<int> ids = dc.TestItems.Where(...).Select(item => item.Id).ToList(); var problematicIds = new List<ErrorType>(); // Either allow the TaskParallel library to select what it considers // as the optimum degree of parallelism by omitting the // ParallelOptions parameter, or specify what you want. Parallel.ForEach(ids, new ParallelOptions {MaxDegreeOfParallelism = 8}, id => CalculateDetails(id, problematicIds)); }
-
Proveďte metodu CalculateDetails s opakovanými pokusy o selhání uváznutí
private static void CalculateDetails(int id, List<ErrorType> problematicIds) { try { // Handle deadlocks DeadlockRetryHelper.Execute(() => CalculateDetails(id)); } catch (Exception e) { // Too many deadlock retries (or other exception). // Record so we can diagnose problem or retry later problematicIds.Add(new ErrorType(id, e)); } }
-
Základní metoda CalculateDetails
private static void CalculateDetails(int id) { // Creating a new DeviceContext is not expensive. // No need to create outside of this method. using (var dc = new TestDataContext()) { // TODO: adjust IsolationLevel to minimize deadlocks // If you don't need to change the isolation level // then you can remove the TransactionScope altogether using (var scope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions {IsolationLevel = IsolationLevel.Serializable})) { TestItem item = dc.TestItems.Single(i => i.Id == id); // work done here dc.SubmitChanges(); scope.Complete(); } } }
-
A samozřejmě moje implementace pomocníka pro opakování uváznutí
public static class DeadlockRetryHelper { private const int MaxRetries = 4; private const int SqlDeadlock = 1205; public static void Execute(Action action, int maxRetries = MaxRetries) { if (HasAmbientTransaction()) { // Deadlock blows out containing transaction // so no point retrying if already in tx. action(); } int retries = 0; while (retries < maxRetries) { try { action(); return; } catch (Exception e) { if (IsSqlDeadlock(e)) { retries++; // Delay subsequent retries - not sure if this helps or not Thread.Sleep(100 * retries); } else { throw; } } } action(); } private static bool HasAmbientTransaction() { return Transaction.Current != null; } private static bool IsSqlDeadlock(Exception exception) { if (exception == null) { return false; } var sqlException = exception as SqlException; if (sqlException != null && sqlException.Number == SqlDeadlock) { return true; } if (exception.InnerException != null) { return IsSqlDeadlock(exception.InnerException); } return false; } }
-
Další možností je použití strategie rozdělení
Pokud lze vaše tabulky přirozeně rozdělit na několik různých sad dat, můžete buď použít rozdělené tabulky a indexy SQL Serveru, nebo můžete existující tabulky rozdělit ručně do několika sad tabulek. Doporučil bych použít rozdělení SQL Serveru, protože druhá možnost by byla chaotická. Také integrované dělení na oddíly je k dispozici pouze v SQL Enterprise Edition.
Pokud je pro vás rozdělení možné, můžete si vybrat schéma rozdělení, které rozdělí vaše data na řekněme 8 různých sad. Nyní můžete použít svůj původní jednovláknový kód, ale mít 8 vláken, z nichž každé cílí na samostatný oddíl. Nyní nedojde k žádnému (nebo alespoň minimálnímu počtu) uváznutí.
Doufám, že to dává smysl.