sql >> Database teknologi >  >> RDS >> Sqlserver

Multi-threading C#-applikation med SQL Server-databasekald

Her er mit bud på problemet:

  • Når du bruger flere tråde til at indsætte/opdatere/forespørge data i SQL Server eller en hvilken som helst database, så er deadlocks et faktum. Du er nødt til at antage, at de vil forekomme og håndtere dem korrekt.

  • Det er ikke sådan, at vi ikke skal forsøge at begrænse forekomsten af ​​dødvande. Det er dog nemt at læse op om de grundlæggende årsager til dødvande og tage skridt til at forhindre dem, men SQL Server vil altid overraske dig :-)

Nogle årsager til dødvande:

  • For mange tråde - prøv at begrænse antallet af tråde til et minimum, men vi vil selvfølgelig gerne have flere tråde for maksimal ydeevne.

  • Ikke nok indekser. Hvis valg og opdateringer ikke er selektive nok, vil SQL fjerne større rækkeviddelåse end sundt. Prøv at angive passende indekser.

  • For mange indekser. Opdatering af indekser forårsager dødvande, så prøv at reducere indekser til det krævede minimum.

  • Transaktionens isolationsniveau er for højt. Standardisolationsniveauet ved brug af .NET er 'Serialiserbar', mens standarden ved brug af SQL Server er 'Read Committed'. At reducere isolationsniveauet kan hjælpe meget (hvis det er relevant selvfølgelig).

Sådan kan jeg løse dit problem:

  • Jeg ville ikke rulle min egen trådløsning, jeg ville bruge TaskParallel-biblioteket. Min primære metode ville se sådan ud:

    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));
    }
    
  • Udfør CalculateDetails-metoden med genforsøg for deadlock-fejl

    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));
        }
    }
    
  • Kernen CalculateDetails-metoden

    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();
            }
        }
    }
    
  • Og selvfølgelig min implementering af en deadlock genforsøgshjælper

    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;
        }
    }
    
  • En yderligere mulighed er at bruge en partitioneringsstrategi

Hvis dine tabeller naturligt kan opdeles i flere forskellige sæt data, kan du enten bruge SQL Server-partitionerede tabeller og indekser, eller du kan manuelt opdele dine eksisterende tabeller i flere sæt tabeller. Jeg vil anbefale at bruge SQL Servers partitionering, da den anden mulighed ville være rodet. Også indbygget partitionering er kun tilgængelig på SQL Enterprise Edition.

Hvis partitionering er muligt for dig, kan du vælge et partitionsskema, der bryder dine data i lad os sige 8 forskellige sæt. Nu kan du bruge din originale enkelttrådede kode, men have 8 tråde, der hver er målrettet mod en separat partition. Nu vil der ikke være nogen (eller i det mindste et minimum antal) dødvande.

Jeg håber, det giver mening.



  1. Sådan ændrer du kolonner eller genererer Alter-scripts ved at bruge GUI i SQL Server - SQL Server / T-SQL vejledning del 38

  2. En oversigt over PostgreSQL Query Caching &Load Balancing

  3. Hvordan undslipper man bogstaveligt procenttegn, når NO_BACKSLASH_ESCAPES-indstillingen er aktiveret?

  4. Sådan beregnes margin i MySQL