Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

Aplikasi C# multi-threading dengan panggilan database SQL Server

Inilah pendapat saya tentang masalahnya:

  • Saat menggunakan banyak utas untuk menyisipkan/memperbarui/meminta data di SQL Server, atau basis data apa pun, maka kebuntuan adalah fakta kehidupan. Anda harus berasumsi bahwa itu akan terjadi dan menanganinya dengan tepat.

  • Bukan berarti kita tidak boleh berusaha membatasi terjadinya kebuntuan. Namun, mudah untuk membaca penyebab dasar dari kebuntuan dan mengambil langkah-langkah untuk mencegahnya, tetapi SQL Server akan selalu mengejutkan Anda :-)

Beberapa alasan kebuntuan:

  • Terlalu banyak utas - coba batasi jumlah utas seminimal mungkin, tetapi tentu saja kami ingin lebih banyak utas untuk kinerja maksimal.

  • Tidak cukup indeks. Jika pemilihan dan pembaruan tidak cukup selektif, SQL akan mengeluarkan kunci rentang yang lebih besar daripada yang sehat. Coba tentukan indeks yang sesuai.

  • Terlalu banyak indeks. Memperbarui indeks menyebabkan kebuntuan, jadi cobalah untuk mengurangi indeks seminimal mungkin.

  • Tingkat isolasi transaksi terlalu tinggi. Tingkat isolasi default saat menggunakan .NET adalah 'Serializable', sedangkan default menggunakan SQL Server adalah 'Read Committed'. Mengurangi tingkat isolasi dapat sangat membantu (tentu saja jika sesuai).

Inilah cara saya mengatasi masalah Anda:

  • Saya tidak akan menggulung solusi threading saya sendiri, saya akan menggunakan perpustakaan TaskParallel. Metode utama saya akan terlihat seperti ini:

    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));
    }
    
  • Jalankan metode HitungDetails dengan percobaan ulang untuk kegagalan kebuntuan

    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));
        }
    }
    
  • Metode inti HitungDetail

    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();
            }
        }
    }
    
  • Dan tentu saja implementasi saya dari deadlock retry helper

    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;
        }
    }
    
  • Satu kemungkinan lebih lanjut adalah dengan menggunakan strategi partisi

Jika tabel Anda secara alami dapat dipartisi menjadi beberapa kumpulan data yang berbeda, maka Anda dapat menggunakan tabel dan indeks yang dipartisi SQL Server, atau Anda dapat secara manual membagi tabel yang ada menjadi beberapa set tabel. Saya akan merekomendasikan menggunakan partisi SQL Server, karena opsi kedua akan berantakan. Juga partisi bawaan hanya tersedia di SQL Enterprise Edition.

Jika mempartisi memungkinkan untuk Anda, Anda dapat memilih skema partisi yang memecah data Anda dalam katakanlah 8 set berbeda. Sekarang Anda dapat menggunakan kode utas tunggal asli Anda, tetapi masing-masing memiliki 8 utas yang menargetkan partisi terpisah. Sekarang tidak akan ada (atau setidaknya jumlah minimum) kebuntuan.

Saya harap itu masuk akal.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. batasan unik bersyarat

  2. Cara paling sederhana untuk melakukan self-join rekursif?

  3. Buat Kolom "Terakhir Dimodifikasi" di SQL Server

  4. grup sql hanya dengan baris yang berurutan

  5. Jalankan prosedur tersimpan menggunakan kerangka entitas