SQLServer中批量插入數據方式的性能對比 (轉)

轉自:http://www.cnblogs.com/wlb/archive/2010/03/02/1676136.html

昨天下午快下班的時候,無意中聽到公司兩位同事在探討批量向數據庫插入數據的性能優化問題,頓時來了興趣,把自己的想法向兩位同事說了一下,于是有了本文。

公司技術背景:數據庫訪問類(xxx.DataBase.Dll)調用存儲過程實現數據庫的訪問。

技術方案一:

壓縮時間下程序員寫出的第一個版本,僅僅為了完成任務,沒有從程序上做任何優化,實現方式是利用數據庫訪問類調用存儲過程,利用循環逐條插入。很明顯,這種方式效率并不高,于是有了前面的兩位同事討論效率低的問題。

技術方案二:

由于是考慮到大數據量的批量插入,于是我想到了ADO.NET2.0的一個新的特性:SqlBulkCopy。有關這個的性能,很早之前我是親自做過性能測試的,效率非常高。這也是我向公司同事推薦的技術方案。

技術方案三:

利用SQLServer2008的新特性--表值參數(Table-Valued Parameter)。表值參數是SQLServer2008才有的一個新特性,使用這個新特性,我們可以把一個表類型作為參數傳遞到函數或存儲過程里。不過,它也有一個特點:表值參數在插入數目少于 1000 的行時具有很好的執行性能。

技術方案四:

對于單列字段,可以把要插入的數據進行字符串拼接,最后再在存儲過程中拆分成數組,然后逐條插入。查了一下存儲過程中參數的字符串的最大長度,然后除以字段的長度,算出一個值,很明顯是可以滿足要求的,只是這種方式跟第一種方式比起來,似乎沒什么提高,因為原理都是一樣的。

技術方案五:

考慮異步創建、消息隊列等等。這種方案無論從設計上還是開發上,難度都是有的。

技術方案一肯定是要被否掉的了,剩下的就是在技術方案二跟技術方案三之間做一個抉擇,鑒于公司目前的情況,技術方案四跟技術方案五就先不考慮了。

接下來,為了讓大家對表值參數的創建跟調用有更感性的認識,我將寫的更詳細些,文章可能也會稍長些,不關注細節的朋友們可以選擇跳躍式的閱讀方式。

再說一下測試方案吧,測試總共分三組,一組是插入數量小于1000的,另外兩組是插入數據量大于1000的(這里我們分別取10000跟1000000),每組測試又分10次,取平均值。怎么做都明白了,Let’s go!

1.創建表。

為了簡單,表中只有一個字段,如下圖所示:

2.創建表值參數類型

我們打開查詢分析器,然后在查詢分析器中執行下列代碼:

Create Type PassportTableType as Table(PassportKey nvarchar(50)
)

執行成功以后,我們打開企業管理器,按順序依次展開下列節點--數據庫、展開可編程性、類型、用戶自定義表類型,就可以看到我們創建好的表值類型了如下圖所示:

說明我們創建表值類型成功了。

3.編寫存儲過程

存儲過程的代碼為:

USE [TestInsert]
GO/****** Object: StoredProcedure [dbo].[CreatePassportWithTVP] Script Date: 03/02/2010 00:14:45 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO-- =============================================-- Author:<Kevin>-- Create date: <2010-3-1>-- Description:<創建通行證>-- =============================================Create PROCEDURE [dbo].[CreatePassportWithTVP]
@TVP PassportTableType readonly
ASBEGINSET NOCOUNT ON;
Insert into Passport(PassportKey) select PassportKey from @TVP
END
可能在查詢分析器中,智能提示會提示表值類型有問題,會出現紅色下劃線(見下圖),不用理會,
繼續運行我們的代碼,完成存儲過程的創建

 

4.編寫代碼調用存儲過程。

三種數據庫的插入方式代碼如下,由于時間比較緊,代碼可能不那么易讀,特別代碼我加了些注釋。

主要部分的代碼
using?System;
using?System.Diagnostics;
using?System.Data;
using?System.Data.SqlClient;
using?com.DataAccess;

namespace?ConsoleAppInsertTest
{
????
class?Program
????{
????????
static?string?connectionString?=?SqlHelper.ConnectionStringLocalTransaction;????//數據庫連接字符串
????????static?int?count?=?1000000;???????????//插入的條數
????????static?void?Main(string[]?args)
????????{
????????????
//long?commonInsertRunTime?=?CommonInsert();
????????????
//Console.WriteLine(string.Format("普通方式插入{1}條數據所用的時間是{0}毫秒",?commonInsertRunTime,?count));

????????????
long?sqlBulkCopyInsertRunTime?=?SqlBulkCopyInsert();
????????????Console.WriteLine(
string.Format("使用SqlBulkCopy插入{1}條數據所用的時間是{0}毫秒",?sqlBulkCopyInsertRunTime,?count));

????????????
long?TVPInsertRunTime?=?TVPInsert();
????????????Console.WriteLine(
string.Format("使用表值方式(TVP)插入{1}條數據所用的時間是{0}毫秒",?TVPInsertRunTime,?count));
????????}

????????
///?<summary>
????????
///?普通調用存儲過程插入數據
????????
///?</summary>
????????
///?<returns></returns>
????????private?static?long?CommonInsert()
????????{
????????????Stopwatch?stopwatch?
=?new?Stopwatch();
????????????stopwatch.Start();
????????????
????????????
string?passportKey;
????????????
for?(int?i?=?0;?i?<?count;?i++)
????????????{
????????????????passportKey?
=?Guid.NewGuid().ToString();
????????????????SqlParameter[]?sqlParameter?
=?{?new?SqlParameter("@passport",?passportKey)?};
????????????????SqlHelper.ExecuteNonQuery(connectionString,?CommandType.StoredProcedure,?
"CreatePassport",?sqlParameter);
????????????}
????????????stopwatch.Stop();
????????????
return?stopwatch.ElapsedMilliseconds;
????????}

????????
///?<summary>
????????
///?使用SqlBulkCopy方式插入數據
????????
///?</summary>
????????
///?<param?name="dataTable"></param>
????????
///?<returns></returns>
????????private?static?long?SqlBulkCopyInsert()
????????{
????????????Stopwatch?stopwatch?
=?new?Stopwatch();
????????????stopwatch.Start();

????????????DataTable?dataTable?
=?GetTableSchema();
????????????
string?passportKey;
????????????
for?(int?i?=?0;?i?<?count;?i++)
????????????{
????????????????passportKey?
=?Guid.NewGuid().ToString();
????????????????DataRow?dataRow?
=?dataTable.NewRow();
????????????????dataRow[
0]?=?passportKey;
????????????????dataTable.Rows.Add(dataRow);
????????????}

????????????SqlBulkCopy?sqlBulkCopy?
=?new?SqlBulkCopy(connectionString);
????????????sqlBulkCopy.DestinationTableName?
=?"Passport";
????????????sqlBulkCopy.BatchSize?
=?dataTable.Rows.Count;
????????????SqlConnection?sqlConnection?
=?new?SqlConnection(connectionString);
????????????sqlConnection.Open();
????????????
if?(dataTable!=null?&&?dataTable.Rows.Count!=0)
????????????{
????????????????sqlBulkCopy.WriteToServer(dataTable);
????????????}
????????????sqlBulkCopy.Close();
????????????sqlConnection.Close();

????????????stopwatch.Stop();
????????????
return?stopwatch.ElapsedMilliseconds;
????????}

????????
private?static?long?TVPInsert()
????????{
????????????Stopwatch?stopwatch?
=?new?Stopwatch();
????????????stopwatch.Start();

????????????DataTable?dataTable?
=?GetTableSchema();
????????????
string?passportKey;
????????????
for?(int?i?=?0;?i?<?count;?i++)
????????????{
????????????????passportKey?
=?Guid.NewGuid().ToString();
????????????????DataRow?dataRow?
=?dataTable.NewRow();
????????????????dataRow[
0]?=?passportKey;
????????????????dataTable.Rows.Add(dataRow);
????????????}

????????????SqlParameter[]?sqlParameter?
=?{?new?SqlParameter("@TVP",?dataTable)?};
????????????SqlHelper.ExecuteNonQuery(connectionString,?CommandType.StoredProcedure,?
"CreatePassportWithTVP",?sqlParameter);

????????????stopwatch.Stop();
????????????
return?stopwatch.ElapsedMilliseconds;
????????}

????????
private?static?DataTable?GetTableSchema()
????????{
????????????DataTable?dataTable?
=?new?DataTable();
????????????dataTable.Columns.AddRange(
new?DataColumn[]?{?new?DataColumn("PassportKey")?});
????????????
????????????
return?dataTable;
????????}

????}
}

?

比較神秘的代碼其實就下面這兩行,該代碼是將一個dataTable做為參數傳給了我們的存儲過程。簡單吧。

SqlParameter[] sqlParameter = { new SqlParameter("@TVP", dataTable) };
SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "CreatePassportWithTVP", sqlParameter);
5.測試并記錄測試結果
第一組測試,插入記錄數1000

第二組測試,插入記錄數10000

第三組測試,插入記錄數1000000

通過以上測試方案,不難發現,技術方案二的優勢還是蠻高的。無論是從通用性還是從性能上考慮,都應該是
優先被選擇的,還有一點,它的技術復雜度要比技術方案三要簡單一些,

設想我們把所有表都創建一遍表值類型,工作量還是有的。因此,我依然堅持我開始時的決定,
向公司推薦使用第二種技術方案。

寫到此,本文就算完了,但是對新技術的鉆研仍然還在不斷繼續。要做的東西還是挺多的。

為了方便大家學習和交流,代碼文件已經打包并上傳了,歡迎共同學習探討。
代碼下載
作者:深山老林
出處:http://wlb.cnblogs.com/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
Tag標簽: kevin,深山老林,SqlBulkCopy,表值參數,Table-Valued Parameter,批量,插入,性能
深山老林
關注 - 9
粉絲 - 38
榮譽:微軟社區精英
關注博主
11
0
0
(請您對文章做出評價)
? 上一篇:再次探擴展-對xVal進行擴展解決驗證不同步的問題
? 下一篇:數據庫訪問的性能問題與瓶頸問題

?

轉載于:https://www.cnblogs.com/lanru/archive/2010/10/15/1852649.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275854.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275854.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

VueConf China 2021 《Vue3生態進展-尤雨溪》 Reaction

大家好&#xff0c;我是若川。今天分享昨天Vueconf的一篇文章&#xff0c;來了解下Vue的生態進展。另外今晚7點&#xff0c;Vuebeijing社區邀請了尤大會在視頻號直播&#xff0c;可以加我微信 ruochuan12&#xff0c;告訴觀看地址提前預約。點擊下方卡片關注我、加個星標&#…

Plsql運行mysql腳本_oracle中PLSQL語句

1.set autot off 禁止使用autotrace命令 set autot on 這個命令包括exp 和 stat(執行語句、生成explain plan、生成統計信息) set autot trace 不執行sql語句&#xff0c;但(生成explain plan、生成統計信息) set autot trace exp stat 與上句同 set autot trace st1.set autot…

2019年,你需要關注這些Node API和Web框架

對于Node.js框架和開源軟件來說&#xff0c;2018年是非常有趣的一年。開發者社區討論了企業贊助對開源項目的作用以及如何維護那些沒有經濟支持卻有數百萬人使用的項目。同樣&#xff0c;安全問題也得到了極大關注&#xff0c;一些流行的Node/JS軟件包被劫持&#xff0c;Github…

ai創造了哪些職業_關于創造職業的思考

ai創造了哪些職業When I was growing up, the idea of a creative career wasn’t an option.當我長大時&#xff0c;創意事業的想法不是一個選擇。 I had enjoyed doodling, arts and crafts as a kid, so as I grew up, it was a natural transition into Photoshop and lat…

Windows Mobile,用C#更改網絡連接(SSID、IP Address、Subnet Mask、Gatew... (轉)

前幾天在做一個改變PDA無線網絡連接的SSID和IP的功能是發現了一個好東西OpenNETCF Framework使用OpenNETCF.Net包&#xff0c;實現了任意改變PDA無線網絡連接的功能。并且不需要Reset PDA。現在正在做一個IP Manager For Windows Mobile的小程序。實現搜索當前網卡可見的SSID、…

一文讀懂vuex4源碼,原來provide/inject就是妙用了原型鏈?

1. 前言你好&#xff0c;我是若川&#xff0c;歡迎加我微信ruochuan12&#xff0c;加群長期交流學習。這是學習源碼整體架構系列 之 vuex4 源碼&#xff08;第十篇&#xff09;。學習源碼整體架構系列文章(有哪些必看的JS庫)&#xff1a;jQuery、underscore、lodash、sentry、v…

Spring4.3x教程之一IOCDI

SpringIOC也稱為DI&#xff0c;對屬性內容的注入可以通過屬性的setXXX方法進行也可以通過構造方法進行&#xff0c;當然還可以使用工廠模式進行屬性內容的注入。 什么是DI&#xff1f;什么是IOC&#xff1f; DI&#xff1a;Dependency Injection依賴注入 其實一個類中的屬性就是…

戰神4 幕后花絮 概念藝術_幕后花絮:品牌更新的背后

戰神4 幕后花絮 概念藝術Under the Hood gives you an inside look at different parts of Waze — straight from the people working on them every day.在引擎蓋下&#xff0c;您可以深入了解Waze的不同部分-直接來自每天進行工作的人員。 Traffic is the worst. It makes …

C#日期控件(js版)

js 腳本代碼: <script type"text/javascript"> //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // 這是一個日歷 Javascript 頁…

python第三周測試_python第三周小測

1.讀取一個文件&#xff0c;顯示除了井號(#)開頭的行意外的所有行# -*- coding: utf-8 -*-"""Created on Tue May 28 09:37:08 2019author: Omega_Sendoh"""#打開文件f open("install-sh","r")#讀取文件的所有行&#xff0…

「Vueconf」探索 Vue3 中 的 JSX

大家好&#xff0c;我是若川。今天再分享 Vueconf 的一篇文章。另外 Vueconf 主辦方提供的錄播鏈接是&#xff1a;?https://www.bilibili.com/read/mobile?id11408693&#xff0c;感興趣可以復制觀看。點擊下方卡片關注我、加個星標。學習源碼整體架構系列、年度總結、JS基礎…

設計模式--享元模式實現C++

/********************************* *設計模式--享元模式實現 *C語言 *Author&#xff1a;WangYong *Blog:http://www.cnblogs.com/newwy ********************************/ #include <iostream> #include <cassert> #include <vector> #include <strin…

安卓加載asset中的json文件_Android解析Asset目錄下的json文件

在app module中的src/main/assets目錄下我們準備了兩個json文件&#xff1a;destination.json如下&#xff1a;{"main/tabs/sofa": {"isFragment": true,"asStarter": false,"needLogin": false,"pageUrl": "main/tabs…

一文搞懂 Promise、Genarator、 Async 三者的區別和聯系

非985/211大學畢業&#xff0c;軟件工程專業&#xff0c;前端&#xff0c;坐標&#xff1a;北京工作三年多&#xff0c;第一家人數 30 多人的創業公司&#xff0c;1 年多。第二家屬于前端技術不錯的公司&#xff0c;2 年多。01我是一個喜歡在技術領域“折騰”的人&#xff0c;技…

閉包,sync使用細節

代碼 先看代碼如下&#xff1a; func main() {var a []intfor i : 0; i < 100; i {go func() {a append(a, i)}()}time.Sleep(2 * time.Second)fmt.Println(a) } 這段測試代碼是想要一個元素為0到100的切片&#xff0c;但是這一小段代碼隱藏了很多的問題。 閉包函數 先看這…

dynamic 儀表板_儀表板完成百萬美元交易

dynamic 儀表板問題 (The Problem) Anybody dealing with tech products and data-focused services runs into the same fundamental problem: what you do is technical but non-technical people control the budget. In other words:任何處理高科技產品和以數據為中心的服務…

checkStyle -- 代碼風格一致

download page: http://sourceforge.net/project/showfiles.php?group_id80344&package_id107587 轉載于:https://www.cnblogs.com/xuqiang/archive/2010/10/26/1953431.html

在線VS Code閱讀源碼神器 github1s

大家好&#xff0c;我是若川。github1s大部分人知道了&#xff0c;但還是有一部分不知道。我在掘金發過沸點和知乎發過想法還是有挺多人不知道&#xff0c;所以再發公眾號推薦下。點擊下方卡片關注我、加個星標。學習源碼整體架構系列、年度總結、JS基礎系列近日&#xff0c;一…

lenze變頻器怎么更改地址_英威騰變頻器GD300維修

英威騰變頻器GD300維修英威騰變頻器GD300維修41. 問題&#xff1a;變頻器跟PLC采用485通訊不上答&#xff1a;1.檢查變頻器的通訊地址是否正確&#xff0c;如果采用通訊啟動&#xff0c;檢查P0.01是否為1&#xff0c;如果通過通訊設定頻率&#xff0c;檢查P0.068&#xff0c;P0…

代碼設計的基礎原則_設計原則:良好設計的基礎

代碼設計的基礎原則As designers, it’s our goal to pass information in the most pleasing way possible. Starting out, there’s a wealth of literature to read and videos to watch that can get quite overwhelming to take in at a glance. People take different ro…