sql >> Databáze >  >> RDS >> Sqlserver

Vícevláknová C# aplikace s voláním databáze SQL Server

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.



  1. Jak nainstalovat MySQL na Windows 10? – Vaše jediné řešení pro instalaci MySQL

  2. Uložená procedura Oracle:vrátí sadu výsledků i parametry out

  3. Vyberte posledních N řádků z MySQL

  4. sp_add_schedule vs sp_add_jobschedule v SQL Server:Jaký je rozdíl?