詳解C#中利用ODP實(shí)現(xiàn)瞬間導(dǎo)入百萬級(jí)數(shù)據(jù)
.NET程序中可以通過ODP調(diào)用特性,對(duì)Oracle數(shù)據(jù)庫進(jìn)行操作,今天來講一下數(shù)據(jù)批量插入的功能,所用技術(shù)不高不深,相信很多朋友都接觸過,小弟班門弄斧了,呵呵。這篇文章是上篇文章的續(xù)集,因?yàn)樯弦淮卧囼?yàn)的征集結(jié)果沒有突破4秒的方法,所以這次繼續(xù)挑戰(zhàn)與挖掘新方法,雖然是Oracle,但仍具有一定收藏意義。
上一次文章中提及的試驗(yàn):
極限挑戰(zhàn)—C#100萬條數(shù)據(jù)導(dǎo)入SQL SERVER數(shù)據(jù)庫僅用4秒 (附源碼)
http://www.cnblogs.com/isline/archive/2010/03/18/1688783.html
這個(gè)試驗(yàn)是針對(duì)SQL SERVER數(shù)據(jù)庫的,宿主環(huán)境也是.NET,有興趣的朋友可以將這兩個(gè)試驗(yàn)對(duì)比一下,為日后工作批量導(dǎo)數(shù)提供支持。
另外,一些朋友對(duì)上次試驗(yàn)環(huán)境有些異議,認(rèn)為應(yīng)該對(duì)數(shù)據(jù)庫和服務(wù)器做優(yōu)化或設(shè)置,以體現(xiàn)試驗(yàn)最終的時(shí)間結(jié)果。這個(gè)固然會(huì)影響試驗(yàn)的時(shí)間結(jié)果,但考慮到在試驗(yàn)環(huán)境中,對(duì)數(shù)據(jù)庫優(yōu)化的標(biāo)準(zhǔn)與優(yōu)化程度不便統(tǒng)一與定量,試驗(yàn)結(jié)果也不易說明其影響源,所以這次試驗(yàn)依然以標(biāo)準(zhǔn)數(shù)據(jù)庫建庫后的配置為主,試驗(yàn)所在服務(wù)器硬件環(huán)境與上次試驗(yàn)保持一致。實(shí)驗(yàn)?zāi)康脑谟谕诰?、?duì)比宿主程序中的數(shù)據(jù)批量操作方法。
有新方法提升性能時(shí)間指標(biāo)的朋友,歡迎互相切磋,互相提高,嘴上功夫就免了。。。
好了正文開始。
普通肉墊式
什么叫批量插入呢,就是一次性插入一批數(shù)據(jù),我們可以把這批數(shù)據(jù)理解為一個(gè)大的數(shù)組,而這些全部只通過一個(gè)SQL來實(shí)現(xiàn),而在傳統(tǒng)方式下,需要調(diào)用很多次的SQL才可以完成,這就是著名的“數(shù)組綁定”的功能。我們先來看一下傳統(tǒng)方式下,插入多行記錄的操作方式:
- //設(shè)置一個(gè)數(shù)據(jù)庫的連接串,
- 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(); //通過循環(huán)寫入大量的數(shù)據(jù),這種方法顯然是肉墊
- 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());
我們先準(zhǔn)備好程序,但是先不做時(shí)間的測(cè)定,因?yàn)樵诤竺嫖覀儠?huì)用多次循環(huán)的方式來計(jì)算所占用的時(shí)間。
使用ODP特性
看上面的程序,大家都很熟悉,因?yàn)樗鼪]有用到任何ODP的特性,而緊接著我們就要來介紹一個(gè)神奇的程序了,我們看一下代碼,為了更直觀,我把所有的注釋及說明直接寫在代碼里:
- //設(shè)置一個(gè)數(shù)據(jù)庫的連接串
- string connectStr = "User Id=scott;Password=tiger;Data Source=";
- OracleConnection conn = new OracleConnection(connectStr);
- OracleCommand command = new OracleCommand();
- command.Connection = conn; //到此為止,還都是我們熟悉的代碼,下面就要開始嘍
- //這個(gè)參數(shù)需要指定每次批插入的記錄數(shù)
- command.ArrayBindCount = recc;
- //在這個(gè)命令行中,用到了參數(shù),參數(shù)我們很熟悉,但是這個(gè)參數(shù)在傳值的時(shí)候
- //用到的是數(shù)組,而不是單個(gè)的值,這就是它獨(dú)特的地方
- command.CommandText = "insert into dept values(:deptno, :deptname, :loc)";
- conn.Open();
- //下面定義幾個(gè)數(shù)組,分別表示三個(gè)字段,數(shù)組的長(zhǎng)度由參數(shù)直接給出
- int[] deptNo = new int[recc];
- string[] dname = new string[recc];
- string[] loc = new string[recc];
- // 為了傳遞參數(shù),不可避免的要使用參數(shù),下面會(huì)連續(xù)定義三個(gè)
- // 從名稱可以直接看出每個(gè)參數(shù)的含義,不在每個(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)中,先把數(shù)組定義好,而不是像上面那樣直接生成SQL
- for (int i = 0; i < recc; i++)
- {
- deptNo[i] = i;
- dname[i] = i.ToString();
- loc[i] = i.ToString();
- }
- //這個(gè)調(diào)用將把參數(shù)數(shù)組傳進(jìn)SQL,同時(shí)寫入數(shù)據(jù)庫
- command.ExecuteNonQuery();
- sw.Stop();
- System.Diagnostics.Debug.WriteLine("批量插入:" + recc.ToString()
- + "所占時(shí)間:" +sw.ElapsedMilliseconds.ToString());
以上代碼略顯冗長(zhǎng),但是加上注釋后基本也就表達(dá)清楚了。
好了,到目前為止,兩種方式的插入操作程序已經(jīng)完成,就剩下對(duì)比了。我在主函數(shù)處寫了一個(gè)小函數(shù),循環(huán)多次對(duì)兩個(gè)方法進(jìn)行調(diào)用,并且同時(shí)記錄下時(shí)間,對(duì)比函數(shù)如下:
- for (int i = 1; i <= 50; i++)
- {
- Truncate();
- OrdinaryInsert(i * 1000); Truncate();
- BatchInsert(i * 1000);
- }
當(dāng)數(shù)據(jù)量達(dá)到100萬級(jí)別時(shí),所用時(shí)間依然令人滿意,最快一次達(dá)到890毫秒,一般為1秒左右。
記錄數(shù) |
標(biāo)準(zhǔn) |
批處理 |
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 |
其中有些數(shù)據(jù)有些跳躍,可能和數(shù)據(jù)庫本身有關(guān)系,但是大部分?jǐn)?shù)據(jù)已經(jīng)能說明問題了??戳诉@些數(shù)據(jù)后,是不是有些心動(dòng)了?
源程序放了一段時(shí)間直接拷貝貼過來了,可能需要調(diào)試一下才能跑通,不過不是本質(zhì)性問題,對(duì)了如果要測(cè)試別忘記安裝Oracle訪問組件。
原文標(biāo)題:極限挑戰(zhàn)—C#+ODP 100萬條數(shù)據(jù)導(dǎo)入Oracle數(shù)據(jù)庫僅用不到1秒
鏈接:http://www.cnblogs.com/isline/archive/2010/08/31/1813722.html
【編輯推薦】