上一次文章中提及的試驗:
極限挑戰—C#100萬(wàn)條數據導入SQL SERVER數據庫僅用4秒 (附源碼)
http://kb.cnblogs.com/page/73491/
這個(gè)試驗是針對SQL SERVER數據庫的,宿主環(huán)境也是.Net,有興趣的朋友可以將這兩個(gè)試驗對比一下,為日后工作批量導數提供支持。
另外,一些朋友對上次試驗環(huán)境有些異議,認為應該對數據庫和服務(wù)器做優(yōu)化或設置,以體現試驗最終的時(shí)間結果。這個(gè)固然會(huì )影響試驗的時(shí)間結果,但考慮到在試驗環(huán)境中,對數據庫優(yōu)化的標準與優(yōu)化程度不便統一與定量,試驗結果也不易說(shuō)明其影響源,所以這次試驗依然以標準數據庫建庫后的配置為主,試驗所在服務(wù)器硬件環(huán)境與上次試驗保持一致。實(shí)驗目的在于挖掘、對比宿主程序中的數據批量操作方法。
有新方法提升性能時(shí)間指標的朋友,歡迎互相切磋,互相提高,嘴上功夫就免了。。。
好了正文開(kāi)始。
● 普通肉墊式
什么叫批量插入呢,就是一次性插入一批數據,我們可以把這批數據理解為一個(gè)大的數組,而這些全部只通過(guò)一個(gè)SQL來(lái)實(shí)現,而在傳統方式下,需要調用很多次的SQL才可以完成,這就是著(zhù)名的“數組綁定”的功能。我們先來(lái)看一下傳統方式下,插入多行記錄的操作方式:


//設置一個(gè)數據庫的連接串,
string connectStr = "User Id=scott;Password=tiger;Data Source=";
OracleConnection conn = new OracleConnection(connectStr);
OracleCommand command = new OracleCommand();
command.Connection = conn;
conn.Open();
Stopwatch sw = new Stopwatch();
sw.Start();
//通過(guò)循環(huán)寫(xiě)入大量的數據,這種方法顯然是肉墊
for (int i = 0; i < recc; i++)
{
string sql = "insert into dept values(" + i.ToString()
+ "," + i.ToString() + "," + i.ToString() + ")";
command.CommandText = sql;
command.ExecuteNonQuery();
}
sw.Stop();
System.Diagnostics.Debug.WriteLine("普通插入:" + recc.ToString()
+ "所占時(shí)間:" + sw.ElapsedMilliseconds.ToString());
我們先準備好程序,但是先不做時(shí)間的測定,因為在后面我們會(huì )用多次循環(huán)的方式來(lái)計算所占用的時(shí)間。
● 使用ODP特性
看上面的程序,大家都很熟悉,因為它沒(méi)有用到任何ODP的特性,而緊接著(zhù)我們就要來(lái)介紹一個(gè)神奇的程序了,我們看一下代碼,為了更直觀(guān),我把所有的注釋及說(shuō)明直接寫(xiě)在代碼里:


//設置一個(gè)數據庫的連接串
string connectStr = "User Id=scott;Password=tiger;Data Source=";
OracleConnection conn = new OracleConnection(connectStr);
OracleCommand command = new OracleCommand();
command.Connection = conn;
//到此為止,還都是我們熟悉的代碼,下面就要開(kāi)始嘍
//這個(gè)參數需要指定每次批插入的記錄數
command.ArrayBindCount = recc;
//在這個(gè)命令行中,用到了參數,參數我們很熟悉,但是這個(gè)參數在傳值的時(shí)候
//用到的是數組,而不是單個(gè)的值,這就是它獨特的地方
command.CommandText = "insert into dept values(:deptno, :deptname, :loc)";
conn.Open();
//下面定義幾個(gè)數組,分別表示三個(gè)字段,數組的長(cháng)度由參數直接給出
int[] deptNo = new int[recc];
string[] dname = new string[recc];
string[] loc = new string[recc];
// 為了傳遞參數,不可避免的要使用參數,下面會(huì )連續定義三個(gè)
// 從名稱(chēng)可以直接看出每個(gè)參數的含義,不在每個(gè)解釋了
OracleParameter deptNoParam = new OracleParameter("deptno",
OracleDbType.Int32);
deptNoParam.Direction = ParameterDirection.Input;
deptNoParam.Value = deptNo;
command.Parameters.Add(deptNoParam);
OracleParameter deptNameParam = new OracleParameter("deptname",
OracleDbType.Varchar2);
deptNameParam.Direction = ParameterDirection.Input;
deptNameParam.Value = dname;
command.Parameters.Add(deptNameParam);
OracleParameter deptLocParam = new OracleParameter("loc",
OracleDbType.Varchar2);
deptLocParam.Direction = ParameterDirection.Input;
deptLocParam.Value = loc;
command.Parameters.Add(deptLocParam);
Stopwatch sw = new Stopwatch();
sw.Start();
//在下面的循環(huán)中,先把數組定義好,而不是像上面那樣直接生成SQL
for (int i = 0; i < recc; i++)
{
deptNo[i] = i;
dname[i] = i.ToString();
loc[i] = i.ToString();
}
//這個(gè)調用將把參數數組傳進(jìn)SQL,同時(shí)寫(xiě)入數據庫
command.ExecuteNonQuery();
sw.Stop();
System.Diagnostics.Debug.WriteLine("批量插入:" + recc.ToString()
+ "所占時(shí)間:" +sw.ElapsedMilliseconds.ToString());
以上代碼略顯冗長(cháng),但是加上注釋后基本也就表達清楚了。
好了,到目前為止,兩種方式的插入操作程序已經(jīng)完成,就剩下對比了。我在主函數處寫(xiě)了一個(gè)小函數,循環(huán)多次對兩個(gè)方法進(jìn)行調用,并且同時(shí)記錄下時(shí)間,對比函數如下:
for (int i = 1; i <= 50; i++)
{
Truncate();
OrdinaryInsert(i * 1000);
Truncate();
BatchInsert(i * 1000);
}
當數據量達到100萬(wàn)級別時(shí),所用時(shí)間依然令人滿(mǎn)意,最快一次達到890毫秒,一般為1秒左右。
| 記錄數 | 標準 | 批處理 |
| 1000 | 1545 | 29 |
| 2000 | 3514 | 20 |
| 3000 | 3749 | 113 |
| 4000 | 5737 | 40 |
| 5000 | 6820 | 52 |
| 6000 | 9469 | 72 |
| 7000 | 10226 | 69 |
| 8000 | 15280 | 123 |
| 9000 | 11475 | 83 |
| 10000 | 14536 | 121 |
| 11000 | 15705 | 130 |
| 12000 | 16548 | 145 |
| 13000 | 18765 | 125 |
| 14000 | 20393 | 116 |
| 15000 | 22181 | 159 |
其中有些數據有些跳躍,可能和數據庫本身有關(guān)系,但是大部分數據已經(jīng)能說(shuō)明問(wèn)題了??戳诉@些數據后,是不是有些心動(dòng)了?
源程序放了一段時(shí)間直接拷貝貼過(guò)來(lái)了,可能需要調試一下才能跑通,不過(guò)不是本質(zhì)性問(wèn)題,對了如果要測試別忘記安裝Oracle訪(fǎng)問(wèn)組件。
聯(lián)系客服