性能优化你必须知道的那些事儿

       最近有客户反馈系统导入EXECL进行数据处理超时了,我当时的第一反应,不可能啊我明明是做过性能优化的啊,怎么还会超时呢,这是要有多少条数据才可能发生啊!于是找客户要来了EXECL,发现有7500多条数据,备份完客户数据库进行代码调试找出性能差的地方。都是一些平时老生常谈的东西,可是又是很容易忽略的地方,这里面就只谈两个点,使用String还是StringBuilder,校验数据正确性是在循环里面一条一条的使用SQL取数呢,还是一次性取出来在代码里面进行校验!下面将用实际数据结合图表,给出准确的答案。

阅读目录

回到顶部

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);
        }

执行结果如下图:

 绘制成曲线图:

   从上图可直观看出来,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执行速度慢,可以采用执行计划看看是否表缺少索引。

      好了本篇到这里就要结束了,如果觉得对你有益,记住点赞哦!

   相关阅读:附加没有日志文件的数据库方法 删除数据库日志文件的方法 数据字典生成工具系列文章

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏tkokof 的技术,小趣及杂念

侃侃哈希表

说到哈希表,相信初通数据结构的人士应该耳熟能详,其相关的结构细节虽然并不繁复,但就快速查找数据而言,该结构优异的性能表现绝对可算一枝独秀,平均情况下O(1)的时...

461
来自专栏熊二哥

Java核心编程快速入门

Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示。 ? ? 反射reflect是理解Java语言...

2019
来自专栏大内老A

也谈事件(Event)

最近园子里发表了一些讨论“事件(Event)”的文章,我也来凑个热闹,谈谈我对事件的一些粗浅的认识。本文不谈设计模式(观察者模式),只从运行时的角度来分析事件这...

1826
来自专栏技术小黑屋

一个事半功倍的Java反射库

在Java和Android中,我们常常会使用反射来达到一些兼容的目的。Java原生提供的反射很是麻烦,使用起来很是不方便。比如我们想要调UserManager的...

682
来自专栏码匠的流水账

reactor-netty中TcpClient的create过程

本文主要研究一下reactor-netty中TcpClient的create的过程

551
来自专栏大内老A

深入理解C# 3.x的新特性(2):Extension Method[下篇]

四、Extension Method的本质 通过上面一节的介绍,我们知道了在C#中如何去定义一个Extension Method:它是定义在一个Static c...

1829
来自专栏青蛙要fly的专栏

项目需求讨论-自定义滚轮

大家好,这次又是到了实际的项目需求讨论时间,我的一些文章下面,有时候有人评论,求源码,求Demo,我的主张是仔细看文章,自己理解了再写一遍,会懂得更多。大部分人...

772
来自专栏salesforce零基础学习

salesforce零基础学习(七十五)浅谈SOSL(Salesforce Object Search Language)

在工作中,我们更多操作的是一个表的对象,所以我们对SOQL的使用很多。但是有时候,我们需要对几个表进行查询操作,类似salesforce的全局搜索功能,这时,使...

2055
来自专栏JackieZheng

Hadoop阅读笔记(三)——深入MapReduce排序和单表连接

  继上篇了解了使用MapReduce计算平均数以及去重后,我们再来一探MapReduce在排序以及单表关联上的处理方法。 在MapReduce系列的第一篇就有...

2167
来自专栏木东居士的专栏

通过源码分析 String、StringBuffer 和 StringBuilder

1463

扫码关注云+社区