性能優化你必須知道的那些事兒

       最近有客戶反饋系統導入EXECL進行數據處理超時了,我當時的第一反應,不可能啊我明明是作過性能優化的啊,怎麼還會超時呢,這是要有多少條數據纔可能發生啊!因而找客戶要來了EXECL,發現有7500多條數據,備份完客戶數據庫進行代碼調試找出性能差的地方。都是一些平時老生常談的東西,但是又是很容易忽略的地方,這裏面就只談兩個點,使用String仍是StringBuilder,校驗數據正確性是在循環裏面一條一條的使用SQL取數呢,仍是一次性取出來在代碼裏面進行校驗!下面將用實際數據結合圖表,給出準確的答案。html

String和StringBuilder性能差別比較

   String和StringBuilder的差異這裏就不提了,學習和工做中經常會聽到拼接字符串要使用StringBuilder對象速度很快,可是可能你只是知道這個知識,實際開發工做中有關注過這一點嗎?我也是當客戶反饋以後本身跟蹤用實際效果才學會這個知識,後續開發中也會銘記這一點!下面的實際數據或許能說明些問題。數據庫

      分別調用了這個函數,   循環次數爲 1,5,15,200,500,1500,2500,5500,8500,20000  後面數據能夠下載最後的DEMO實驗一下,String在這時已是慢到不行了。爲了保證數據的準確性,這裏每一個量級的數據都取了十次值,而後求出平均值。性能優化

 /// <summary>
        /// 對比String和StringBuilder拼接字符串的速度
        /// 每種量級測試,取十次時間平均值
        /// </summary>
        /// <param name="Total">循環次數</param>
        public static void StringSpeedComparer(int Total){
            List<string> list = new List<string>();
            for (int i = 0; i < Total; i++)
            {
                list.Add(Guid.NewGuid().ToString());
            }

            int iTest = 10;
            //總執行時間 ms
            double TotalMilliseconds = 0;


            //String拼接
            string strGUID = String.Empty;
            while (iTest > 0)
            {
                DateTime dtBegin = DateTime.Now;
                foreach (string temp in list)
                {
                    strGUID = strGUID + temp + ";";
                }
                DateTime dtEnd = DateTime.Now;
                TotalMilliseconds += (dtEnd - dtBegin).TotalMilliseconds;
                iTest--;
            }
            Console.WriteLine("String拼接{0}個字符串耗時{1}ms", Total, TotalMilliseconds / 10);

            //StringBuilder拼接
            StringBuilder sb = new StringBuilder();
            iTest = 10;
            TotalMilliseconds = 0;
            while (iTest > 0)
            {
                DateTime dtBegin = DateTime.Now;
                foreach (string temp in list)
                {
                    sb.AppendFormat("{0};", temp);
                }
                DateTime dtEnd = DateTime.Now;
                TotalMilliseconds += (dtEnd - dtBegin).TotalMilliseconds;
                iTest--;
            }
            Console.WriteLine("StringBuilder拼接{0}個字符串耗時{1}ms", Total, TotalMilliseconds / 10);
        }

執行結果以下圖:ide

  

 繪製成曲線圖:函數

   

   從上圖可直觀看出來,String拼接是呈幾何形遞增的,而StringBuilder呈線性的,遞增趨勢很慢。在循環次數多的狀況下使用哪一種拼接,相信你們都清楚了吧!在7500的數量時,能夠節省整整4s的時間,性能是否是提高不少呢?工具

循環取數仍是一次性取數?

  背景:EXECL中有7500行學生信息數據,要把這些數據導入到學生表(p_Student)裏面,可是要保證學生編號(StudentNo)惟一,不惟一導入的時候須要給出提示信息。這就須要在後臺代碼裏面讀取EXECL裏面的學生信息而後校驗學生編碼在數據庫中是否存在,固然EXECL中填寫的學生編號也要校驗惟一。下面就來模擬這個過程,以兩種方式比較性能。、性能

  首先建立學生信息表,插入7500條數據,下面是SQL腳本,學生編號這裏插入的是newid,實際狀況不會是這樣的,這裏只是會了保證惟一,可是又是無序的,儘量模擬真實情形。學習

/*---------------------------數據字典生成工具(V2.1)--------------------------------*/
GO
IF NOT EXISTS(SELECT 1 FROM sysobjects WHERE id=OBJECT_ID('[p_Student]'))
BEGIN
/*==============================================================*/
/* Table: p_Student                                              */
/*==============================================================*/
CREATE TABLE [dbo].[p_Student](
    [StudentGUID] uniqueidentifier   ,
    [Name] varchar(40)   ,
    [Major] varchar(100)   ,
    [Sex] varchar(8)   ,
    [StudentNo] varchar(100)   ,
    PRIMARY KEY(StudentGUID)
)
    

declare @CurrentUser sysname
select @CurrentUser = user_name()
execute sp_addextendedproperty 'MS_Description', '學生信息表','user', @CurrentUser, 'table', 'p_Student'
execute sp_addextendedproperty 'MS_Description',  '學生信息GUID' ,'user', @CurrentUser, 'table', 'p_Student', 'column', 'StudentGUID'
execute sp_addextendedproperty 'MS_Description',  '姓名' ,'user', @CurrentUser, 'table', 'p_Student', 'column', 'Name'
execute sp_addextendedproperty 'MS_Description',  '專業' ,'user', @CurrentUser, 'table', 'p_Student', 'column', 'Major'
execute sp_addextendedproperty 'MS_Description',  '性別' ,'user', @CurrentUser, 'table', 'p_Student', 'column', 'Sex'
execute sp_addextendedproperty 'MS_Description',  '學生編號' ,'user', @CurrentUser, 'table', 'p_Student', 'column', 'StudentNo'

END
GO
--插入7500條模擬數據
DECLARE @Count AS INT
SELECT @Count=COUNT(1) FROM p_Student
IF @Count=0
BEGIN
    DECLARE @i AS INT
    SET @i=7500
    WHILE @i>0
    BEGIN
        INSERT INTO dbo.p_Student
                ( StudentGUID ,
                  Name ,
                  Major ,
                  Sex ,
                  StudentNo
                )
        VALUES  ( NEWID() , -- StudentGUID - uniqueidentifier
                  @i , -- Name - varchar(40)
                  '軟件工程' , -- Major - varchar(100)
                  '' , -- Sex - varchar(8)
                  NEWID()  -- StudentNo - varchar(100)
                )
        SET @i=@i-1
    END
END
GO

      基礎信息準備好之後,進入後臺代碼測試

 /// <summary>
        /// 統計循環校驗和一次性校驗性能差別
        /// </summary>
        public static void Check(int Total)
        {
            //這裏模擬學生編號
            List<string> listStudetNo = new List<string>();
            for (int i = 0; i < Total; i++)
            {
                listStudetNo.Add(Guid.NewGuid().ToString());
            }
            using (SqlConnection con = new SqlConnection(SqlCon))
            {
                con.Open();
                string strSQL = "SELECT COUNT(1) FROM dbo.p_Student WHERE StudentNo='{0}'";
                SqlCommand cmd = con.CreateCommand();

                //循環校驗
                double TotalMilliseconds = 0;
                for (int i = 0; i < 10; i++)
                {
                    foreach (string studentNo in listStudetNo)
                    {
                        DateTime dtBegin = DateTime.Now;
                        cmd.CommandText = String.Format(strSQL, studentNo);
                        int count = (int)cmd.ExecuteScalar();
                        if (count > 0)
                        {
                            Console.WriteLine("{0}編號重複,請從新錄入!", studentNo);
                            return;
                        }
                        DateTime dtEnd = DateTime.Now;
                        TotalMilliseconds += (dtEnd - dtBegin).TotalMilliseconds;
                    }
                }
                Console.WriteLine("循環校驗{0}個學生編號耗時{1}ms", Total, TotalMilliseconds / 10);

                //一次性校驗
                TotalMilliseconds = 0;
                strSQL = "SELECT TOP 1 StudentNo FROM dbo.p_Student WHERE StudentNo IN ('{0}')";
                for (int i = 0; i < 10; i++)
                {
                    DateTime dtBegin = DateTime.Now;
                    StringBuilder sb = new StringBuilder();
                    foreach (string studentNo in listStudetNo)
                    {
                        sb.AppendFormat("{0};", studentNo);
                    }
                    cmd.CommandText = String.Format(strSQL,sb.ToString().Substring(0, sb.ToString().Length - 1).Replace(";","','"));
                    string no = (string)cmd.ExecuteScalar();
                    if (!string.IsNullOrEmpty(no))
                    {
                        Console.WriteLine("{0}編號重複,請從新錄入!", no);
                        return;
                    }
                    DateTime dtEnd = DateTime.Now;
                    TotalMilliseconds += (dtEnd - dtBegin).TotalMilliseconds;
                }
                Console.WriteLine("一次性校驗{0}個學生編號耗時{1}ms", Total, TotalMilliseconds / 10);
            }
        }

    從上圖可直觀看出來,循環校驗和一次性校驗都是線性遞增的,一次性校驗速度差很少比循環的快一倍左右。

示例下載及總結

         示例sql示例代碼DEMO

         其實性能優化不只僅只有這麼一點,須要在平常工做中總結,此次性能優化還有一點也令我驚歎,有一條SQL未優化以前執行須要20s左右,給表添加了索引,速度刷的一下變成0s了,最終性能問題圓滿解決了。

        性能優化思想:

        1:大量字符串拼接請採用StringBuilder

        2:千萬不要在大量循環裏面循環查SQL,考慮是否能用一次性查詢代替,或者一次性把數據查詢出來在代碼裏面進行邏輯判斷

        3:SQL執行速度慢,能夠採用執行計劃看看是否表缺乏索引。

      好了本篇到這裏就要結束了,若是以爲對你有益,記住點贊哦!

  

   相關閱讀:附加沒有日誌文件的數據庫方法  刪除數據庫日誌文件的方法  數據字典生成工具系列文章

相關文章
相關標籤/搜索