Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Многонишково C# приложение с извиквания на база данни на SQL Server

Ето моето мнение за проблема:

  • Когато използвате множество нишки за вмъкване/актуализиране/запитване на данни в SQL Server или която и да е база данни, тогава задръстванията са факт от живота. Трябва да приемете, че ще се случат и да се справите с тях по подходящ начин.

  • Това не означава, че не трябва да се опитваме да ограничаваме появата на безизходи. Въпреки това е лесно да прочетете основните причини за блокиране и да предприемете стъпки за предотвратяването им, но SQL Server винаги ще ви изненада :-)

Някаква причина за блокиране:

  • Твърде много нишки – опитайте се да ограничите броя на нишките до минимум, но, разбира се, искаме повече нишки за максимална производителност.

  • Няма достатъчно индекси. Ако изборите и актуализациите не са достатъчно селективни, SQL ще премахне заключванията на по-голям диапазон, отколкото е здравословно. Опитайте се да посочите подходящи индекси.

  • Твърде много индекси. Актуализирането на индекси води до блокиране, така че опитайте да намалите индексите до необходимия минимум.

  • Нивото на изолация на транзакциите е твърде високо. Нивото на изолация по подразбиране при използване на .NET е „Serializable“, докато стандартното за използване на SQL Server е „Read Committed“. Намаляването на нивото на изолация може да помогне много (ако е подходящо, разбира се).

Ето как мога да се справя с проблема ви:

  • Не бих разгръщал собственото си решение за нишки, бих използвал библиотеката TaskParallel. Основният ми метод би изглеждал така:

    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));
    }
    
  • Изпълнете метода CalculateDetails с повторни опити за неуспехи в застой

    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));
        }
    }
    
  • Основният метод 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();
            }
        }
    }
    
  • И, разбира се, моята реализация на помощник за повторен опит в застой

    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;
        }
    }
    
  • Друга възможност е да използвате стратегия за разделяне

Ако вашите таблици могат естествено да бъдат разделени на няколко отделни набора от данни, тогава можете или да използвате разделени таблици и индекси на SQL Server, или можете ръчно да разделите съществуващите си таблици на няколко набора от таблици. Бих препоръчал да използвате разделянето на SQL Server, тъй като втората опция би била разхвърляна. Също така вграденото разделяне е достъпно само в SQL Enterprise Edition.

Ако разделянето е възможно за вас, можете да изберете схема за разделяне, която да разбие вашите данни в да кажем 8 различни набора. Сега можете да използвате оригиналния си единичен код, но да имате 8 нишки, всяка от които е насочена към отделен дял. Сега няма да има никакви (или поне минимален брой) блокирания.

Надявам се, че има смисъл.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 4 невероятни ресурси за наблюдение на SQL Server за администратори на бази данни

  2. SQL OVER() клаузата – кога и защо е полезна?

  3. Как да генерирате изявление за добавяне на колона за всички таблици в база данни в SQL Server - SQL Server / T-SQL урок, част 49

  4. Дата / клеймо за час за запис, когато записът е добавен към таблицата?

  5. Изключване на дял в SQL Server (T-SQL)