最近有客戶反饋系統導入EXECL進行數據處理超時了,我當時的第一反應,不可能啊我明明是做過性能優化的啊,怎麼還會超時呢,這是要有多少條數據才可能發生啊!於是找客戶要來了EXECL,發現有7500多條數據,備份完客戶數據庫進行代碼調試找出性能差的地方。都是一些平時老生常談的東西,可是又是很容易忽略的地方,這裡面就只談兩個點,使用String還是StringBuilder,校驗數據正確性是在循環裡面一條一條的使用SQL取數呢,還是一次性取出來在代碼裡面進行校驗!下面將用實際數據結合圖表,給出准確的答案。
閱讀目錄
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執行速度慢,可以采用執行計劃看看是否表缺少索引。
好了本篇到這裡就要結束了,如果覺得對你有益,記住點贊哦!
相關閱讀:附加沒有日志文件的數據庫方法 刪除數據庫日志文件的方法 數據字典生成工具系列文章
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新博客,不妨點擊一下綠色通道的【關注我】。
因為,我的寫作熱情也離不開您的肯定支持。
感謝您的閱讀,如果您對我的博客所講述的內容有興趣,請繼續關注我的後續博客,我是焰尾迭 。