SQLServer批量插入數(shù)據(jù)的三種方式及性能對(duì)比
昨天下午快下班的時(shí)候,無意中聽到公司兩位同事在探討批量向數(shù)據(jù)庫插入數(shù)據(jù)的性能優(yōu)化問題,頓時(shí)來了興趣,把自己的想法向兩位同事說了一下,于是有了本文。
公司技術(shù)背景:數(shù)據(jù)庫訪問類(xxx.DataBase.Dll)調(diào)用存儲(chǔ)過程實(shí)現(xiàn)數(shù)據(jù)庫的訪問。
技術(shù)方案一:
壓縮時(shí)間下程序員寫出的第一個(gè)版本,僅僅為了完成任務(wù),沒有從程序上做任何優(yōu)化,實(shí)現(xiàn)方式是利用數(shù)據(jù)庫訪問類調(diào)用存儲(chǔ)過程,利用循環(huán)逐條插入。很明顯,這種方式效率并不高,于是有了前面的兩位同事討論效率低的問題。
技術(shù)方案二:
由于是考慮到大數(shù)據(jù)量的批量插入,于是我想到了ADO.NET2.0的一個(gè)新的特性:SqlBulkCopy。有關(guān)這個(gè)的性能,很早之前我是親自做過性能測(cè)試的,效率非常高。這也是我向公司同事推薦的技術(shù)方案。
技術(shù)方案三:
利用SQLServer2008的新特性--表值參數(shù)(Table-Valued Parameter)。表值參數(shù)是SQLServer2008才有的一個(gè)新特性,使用這個(gè)新特性,我們可以把一個(gè)表類型作為參數(shù)傳遞到函數(shù)或存儲(chǔ)過程里。不過,它也有一個(gè)特點(diǎn):表值參數(shù)在插入數(shù)目少于 1000 的行時(shí)具有很好的執(zhí)行性能。
技術(shù)方案四:
對(duì)于單列字段,可以把要插入的數(shù)據(jù)進(jìn)行字符串拼接,最后再在存儲(chǔ)過程中拆分成數(shù)組,然后逐條插入。查了一下存儲(chǔ)過程中參數(shù)的字符串的最大長(zhǎng)度,然后除以字段的長(zhǎng)度,算出一個(gè)值,很明顯是可以滿足要求的,只是這種方式跟第一種方式比起來,似乎沒什么提高,因?yàn)樵矶际且粯拥摹?/p>
技術(shù)方案五:
考慮異步創(chuàng)建、消息隊(duì)列等等。這種方案無論從設(shè)計(jì)上還是開發(fā)上,難度都是有的。
技術(shù)方案一肯定是要被否掉的了,剩下的就是在技術(shù)方案二跟技術(shù)方案三之間做一個(gè)抉擇,鑒于公司目前的情況,技術(shù)方案四跟技術(shù)方案五就先不考慮了。
接下來,為了讓大家對(duì)表值參數(shù)的創(chuàng)建跟調(diào)用有更感性的認(rèn)識(shí),我將寫的更詳細(xì)些,文章可能也會(huì)稍長(zhǎng)些,不關(guān)注細(xì)節(jié)的朋友們可以選擇跳躍式的閱讀方式。
再說一下測(cè)試方案吧,測(cè)試總共分三組,一組是插入數(shù)量小于1000的,另外兩組是插入數(shù)據(jù)量大于1000的(這里我們分別取10000跟1000000),每組測(cè)試又分10次,取平均值。怎么做都明白了,Let's go!
1.創(chuàng)建表。
為了簡(jiǎn)單,表中只有一個(gè)字段,如下圖所示:
2.創(chuàng)建表值參數(shù)類型
我們打開查詢分析器,然后在查詢分析器中執(zhí)行下列代碼:
Create Type PassportTableType as Table ( PassportKey nvarchar(50) )
執(zhí)行成功以后,我們打開企業(yè)管理器,按順序依次展開下列節(jié)點(diǎn)--數(shù)據(jù)庫、展開可編程性、類型、用戶自定義表類型,就可以看到我們創(chuàng)建好的表值類型了如下圖所示:
說明我們創(chuàng)建表值類型成功了。
3.編寫存儲(chǔ)過程
存儲(chǔ)過程的代碼為:
USE [TestInsert] GO /****** Object: StoredProcedure [dbo].[CreatePassportWithTVP] Script Date: 03/02/2010 00:14:45 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <Kevin> -- Create date: <2010-3-1> -- Description: <創(chuàng)建通行證> -- ============================================= Create PROCEDURE [dbo].[CreatePassportWithTVP] @TVP PassportTableType readonly AS BEGIN SET NOCOUNT ON; Insert into Passport(PassportKey) select PassportKey from @TVP END
可能在查詢分析器中,智能提示會(huì)提示表值類型有問題,會(huì)出現(xiàn)紅色下劃線(見下圖),不用理會(huì),繼續(xù)運(yùn)行我們的代碼,完成存儲(chǔ)過程的創(chuàng)建
4.編寫代碼調(diào)用存儲(chǔ)過程。
三種數(shù)據(jù)庫的插入方式代碼如下,由于時(shí)間比較緊,代碼可能不那么易讀,特別代碼我加了些注釋。
using System; using System.Diagnostics; using System.Data; using System.Data.SqlClient; using com.DataAccess; namespace ConsoleAppInsertTest { class Program { static string connectionString = SqlHelper.ConnectionStringLocalTransaction; //數(shù)據(jù)庫連接字符串 static int count = 1000000; //插入的條數(shù) static void Main(string[] args) { //long commonInsertRunTime = CommonInsert(); //Console.WriteLine(string.Format("普通方式插入{1}條數(shù)據(jù)所用的時(shí)間是{0}毫秒", commonInsertRunTime, count)); long sqlBulkCopyInsertRunTime = SqlBulkCopyInsert(); Console.WriteLine(string.Format("使用SqlBulkCopy插入{1}條數(shù)據(jù)所用的時(shí)間是{0}毫秒", sqlBulkCopyInsertRunTime, count)); long TVPInsertRunTime = TVPInsert(); Console.WriteLine(string.Format("使用表值方式(TVP)插入{1}條數(shù)據(jù)所用的時(shí)間是{0}毫秒", TVPInsertRunTime, count)); } /// <summary> /// 普通調(diào)用存儲(chǔ)過程插入數(shù)據(jù) /// </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方式插入數(shù)據(jù) /// </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; } } }
比較神秘的代碼其實(shí)就下面這兩行,該代碼是將一個(gè)dataTable做為參數(shù)傳給了我們的存儲(chǔ)過程。簡(jiǎn)單吧。
SqlParameter[] sqlParameter = { new SqlParameter("@TVP", dataTable) }; SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "CreatePassportWithTVP", sqlParameter);
5.測(cè)試并記錄測(cè)試結(jié)果
第一組測(cè)試,插入記錄數(shù)1000
第二組測(cè)試,插入記錄數(shù)10000
第三組測(cè)試,插入記錄數(shù)1000000
通過以上測(cè)試方案,不難發(fā)現(xiàn),技術(shù)方案二的優(yōu)勢(shì)還是蠻高的。無論是從通用性還是從性能上考慮,都應(yīng)該是優(yōu)先被選擇的,還有一點(diǎn),它的技術(shù)復(fù)雜度要比技術(shù)方案三要簡(jiǎn)單一些,設(shè)想我們把所有表都創(chuàng)建一遍表值類型,工作量還是有的。因此,我依然堅(jiān)持我開始時(shí)的決定,向公司推薦使用第二種技術(shù)方案。
寫到此,本文就算完了,但是對(duì)新技術(shù)的鉆研仍然還在不斷繼續(xù)。要做的東西還是挺多的。
為了方便大家學(xué)習(xí)和交流,代碼文件已經(jīng)打包并上傳了,歡迎共同學(xué)習(xí)探討。
到此這篇關(guān)于SQLServer批量插入數(shù)據(jù)的三種方式及性能對(duì)比的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
深入解析C#設(shè)計(jì)模式中對(duì)橋接模式的具體運(yùn)用
這篇文章主要介紹了C#設(shè)計(jì)模式中對(duì)橋接模式的具體運(yùn)用,橋接模式所強(qiáng)調(diào)的解耦在代碼維護(hù)中非常有用,需要的朋友可以參考下2016-02-02C#程序啟動(dòng)項(xiàng)的設(shè)置方法
這篇文章主要為大家詳細(xì)介紹了C#程序啟動(dòng)項(xiàng)的設(shè)置方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11C#/VB.NET 給Excel添加、刪除數(shù)字簽名的方法
這篇文章主要介紹了C#/VB.NET 給Excel添加、刪除數(shù)字簽名的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11C#復(fù)合模式(Composite Pattern)實(shí)例教程
這篇文章主要介紹了C#復(fù)合模式(Composite Pattern),以實(shí)例形式講述了復(fù)合模式在樹形結(jié)構(gòu)中的應(yīng)用,需要的朋友可以參考下2014-09-09基于c#實(shí)現(xiàn)的九九乘法表(簡(jiǎn)單實(shí)例)
本文主要分享了基于c#實(shí)現(xiàn)的九九乘法表,代碼簡(jiǎn)潔,需要的朋友可以參考下,希望對(duì)大家有所幫助2016-12-12