.Net 環境下比較各類數據庫插入操做的性能

一、簡介sql

再說Windows的異步I/O操做前,先聊聊一些題外話,能幫助咱們更好的理解異步I/O操做,常規的Web程序,當用戶發起一次請求,當請求經過管道到達客戶端的這個過程,會喚起一個線程池線程(後臺線程),處理咱們的業務代碼,即全部的用戶請求是經過異步的方式發起的,這個過程,.Net Framework會自動進行,即便咱們沒有顯示的經過代碼來實現這個過程.因此這個過程明顯是存在性能瓶頸的,假設如今有一個4核服務器,意味這該服務器同時只能處理4個用戶請求(超理想狀況下,通常不可能),可是這個時候來了10000個用戶請求(併發執行)的狀況下,那麼意味者大量線程會堆積起來,等待着前面的線程執行完畢,同時進行頻繁的上下文切換,這個時候你會發現CPU會爆表.數據庫

上面只是一個例子,再說一個數據庫的例子,如今須要向數據庫插入20000條記錄,分爲三個版本去實現,第一個版本是單個線程同步插入,第二個版本多線程同步插入(Parallel),第三個版本多線程異步插入,來比較下性能和CPU利用零及使用狀況.api

 

(1)、單線程同步版本服務器

這個場景是隻有一個用戶請求進來,進行20000次的數據庫插入操做,這個版本不會產生線程堆積,由於全部的插入操做都只由主線程完成.多線程

        private static readonly string ConnectionStrings;
        static Program()
        {
            //配置數據庫鏈接
             ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
        }

        static void Main(string[] args)
        {

            var stop = Stopwatch.StartNew();
            InstertSync();
            stop.Stop();
            Console.WriteLine($"同步執行20000次插入操做,耗時:{stop.ElapsedMilliseconds/1000}秒");
            Console.ReadKey();
        }


        private static void InstertSync()
        {
            var totalCount = 0;
            var failCount = 0;

            //這裏以同步方式執行數據庫操做,注這裏只有一個線程執行全部的數據庫插入操做
            for (int i = 0; i <= 20000; i++)
            {
                var conn = new SqlConnection(ConnectionStrings);
                conn.Open();
                try
                {

                    //模擬數據庫耗時操做
                    var sql = "insert into [dbo].[User]([Amount]) values (@Amount)";
                    var command = new SqlCommand(sql, conn);
                    command.Parameters.Add(
                        new SqlParameter("@Amount", i)
                    );
                    //這裏線程會等待這一段時間,等待數據庫返回結果,並繼續執行下面的代碼
                    var result = command.ExecuteNonQuery();
                    if (result == 1)
                    {
                        totalCount+=1;
                    }
                    else
                    {
                        failCount+=1;
                    }

                    Console.WriteLine($"成功插入{totalCount}條記錄,插入失敗{failCount}條記錄");
                }
                catch (Exception ex)
                {

                    throw ex;
                }
                finally
                {
                    conn.Close();
                    conn.Dispose();
                }
            }
        }

再看看數據庫的批請求數數據併發

大概穩定在300次左右每秒異步

 

 

(2)、多線程同步async

這個場景是大多數沒有使用Async Await模型的Web應用程序(Parallel表明同時有多個用戶請求進來),同時數據庫也使用的是同步Api,這個時候以同步的方式發起數據庫請求,每一個線程會等待不肯定的時間,等待數據庫返回結果,同時另外一個線程開啓,也會等待數據庫返回結果,這樣用戶請求一多,就會產生大量的線程堆積,形成大量的內存浪費,並且當數據庫開始響應線程時,線程會被喚醒,所有開始執行,這時候CPU又會開始繁忙的執行.性能

        private static readonly string ConnectionStrings;
        static Program()
        {
            //配置數據庫鏈接
             ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
        }

        static void Main(string[] args)
        {
            InsertAsync();
            Console.ReadKey();
        }

        private static void InsertAsync()
        {
            var stop = Stopwatch.StartNew();
            var totalCount = 0;
            var failCount = 0;
            var res = Parallel.For(0, 20000, i =>
            {
                var conn = new SqlConnection(ConnectionStrings);
                conn.Open();
                try
                {
                    //模擬數據庫耗時操做
                    var sql = "insert into [dbo].[User]([Amount]) values (@Amount)";
                    var command = new SqlCommand(sql, conn);
                    command.Parameters.Add(
                        new SqlParameter("@Amount", i)
                    );
                    var result = command.ExecuteNonQuery();
                    if (result == 1)
                    {
                        Interlocked.Add(ref totalCount, 1);
                    }
                    else
                    {
                        Interlocked.Add(ref failCount, 1);
                    }

                    Console.WriteLine($"成功插入{totalCount}條記錄,插入失敗{failCount}條記錄");
                }
                catch (Exception ex)
                {

                    throw ex;
                }
                finally
                {
                    conn.Close();
                    conn.Dispose();
                }
            });
            if (res.IsCompleted)
            {
                stop.Stop();
                Console.WriteLine($"同步執行20000次插入操做,耗時:{stop.ElapsedMilliseconds / 1000}秒");
            }
        }

去除Interlocked稍稍快一些.明顯能夠發如今多線程環境下,使用同步的數據庫操做api,效率顯著降低.CPU的利用率也很低,同時跑了不少操做線程,但數據庫使用同步Api,只能響應一個線程,其他的都須要排隊.spa

 再看看數據庫批請求數

只能穩定在130次左右,說明多線程環境下,使用同步數據庫操做,阻礙了請求的提交速度.我的理解.

 

(3)、多線程異步

這個場景用戶使用基於Async Await模型的Web程序,且使用數據庫的異步Api

        private static readonly string ConnectionStrings;
        static Program()
        {
            //配置數據庫鏈接
             ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
        }

        static void Main(string[] args)
        {
            InsertAsync();
            Console.ReadKey();
        }

        private static void InsertAsync()
        {
            var stop = Stopwatch.StartNew();
            var totalCount = 0;
            var failCount = 0;
            var res = Parallel.For(0, 20000,async i =>
            {
                var conn = new SqlConnection(ConnectionStrings);
                conn.Open();
                try
                {

                    //模擬數據庫耗時操做
                    var sql = "insert into [dbo].[User]([Amount]) values (@Amount)";
                    var command = new SqlCommand(sql, conn);
                    command.Parameters.Add(
                        new SqlParameter("@Amount", i)
                    );

                    var result =await command.ExecuteNonQueryAsync();
                    if (result == 1)
                    {
                        Interlocked.Add(ref totalCount, 1);
                    }
                    else
                    {
                        Interlocked.Add(ref failCount, 1);
                    }

                    Console.WriteLine($"成功插入");
                }
                catch (Exception ex)
                {

                    throw ex;
                }
                finally
                {
                    conn.Close();
                    conn.Dispose();
                }
            });
            if (res.IsCompleted)
            {
                stop.Stop();
                Console.WriteLine($"同步執行20000次插入操做,耗時:{stop.ElapsedMilliseconds / 1000}秒");
            }
        }

能夠發現這個模式插入效率很是之高.可是它的插入是無序的,由於Parallel執行線程的順序是無序的.CPU的利用率也是極高的.

再看看數據庫批請求數

直線飆升>1000次的請求提交,說明使用異步Api數據庫每秒接收的請求數,遠大於同步方式,也是使用異步Api如此之快的緣由.

相關文章
相關標籤/搜索