网站导航网学 原创论文 原创专题 网站设计 最新系统 原创论文 论文降重 发表论文 论文发表 UI设计定制 论文答辩PPT格式排版 期刊发表 论文专题
返回网学首页
网学原创论文
最新论文 推荐专题 热门论文 论文专题
当前位置: 网学 > 设计资源 > 数据库 > 正文

SQLServer中批量插入数据方式的性能对比(附测试代码)

论文降重修改服务、格式排版等 获取论文 论文降重及排版 论文发表 相关服务

公司技术背景:数据库访问类(xxx.DataBase.Dll)调用存储过程实现数据库的访问。【测试代码下载

技术方案一:

压缩时间下程序员写出的第一个版本,仅仅为了完成任务,没有从程序上做任何优化,实现方式是利用数据库访问类调用存储过程,利用循环逐条插入。很明显,这种方式效率并不高,于是有了前面的两位同事讨论效率低的问题。

技术方案二:

由于是考虑到大数据量的批量插入,于是我想到了ADO.NET2.0的一个新的特性:SqlBulkCopy。有关这个的性能,很早之前我是亲自做过性能测试的,效率非常高。这也是我向公司同事推荐的技术方案。

技术方案三:

利用SQLServer2008的新特性--表值参数(Table-Valued Parameter)。表值参数是SQLServer2008才有的一个新特性,使用这个新特性,我们可以把一个表类型作为参数传递到函数或存储过程里。不过,它也有一个特点:表值参数在插入数目少于 1000 的行时具有很好的执行性能。

技术方案四:

对于单列字段,可以把要插入的数据进行字符串拼接,最后再在存储过程中拆分成数组,然后逐条插入。查了一下存储过程中参数的字符串的最大长度,然后除以字段的长度,算出一个值,很明显是可以满足要求的,只是这种方式跟第一种方式比起来,似乎没什么提高,因为原理都是一样的。

技术方案五:

考虑异步创建、消息队列等等。这种方案无论从设计上还是开发上,难度都是有的。

技术方案一肯定是要被否掉的了,剩下的就是在技术方案二跟技术方案三之间做一个抉择,鉴于公司目前的情况,技术方案四跟技术方案五就先不考虑了。

接下来,为了让大家对表值参数的创建跟调用有更感性的认识,我将写的更详细些,文章可能也会稍长些,不关注细节的朋友们可以选择跳跃式的阅读方式。

再说一下测试方案吧,测试总共分三组,一组是插入数量小于1000的,另外两组是插入数据量大于1000的(这里我们分别取10000跟1000000),每组测试又分10次,取平均值。怎么做都明白了,Let’s go!

1.创建表。

为了简单,表中只有一个字段,如下图所示:

2.创建表值参数类型

我们打开查询分析器,然后在查询分析器中执行下列代码:

  1. Create Type PassportTableType as Table  
  2. (  
  3. PassportKey nvarchar(50)  
  4.  

执行成功以后,我们打开企业管理器,按顺序依次展开下列节点--数据库、展开可编程性、类型、用户自定义表类型,就可以看到我们创建好的表值类型了如下图所示:

说明我们创建表值类型成功了。

3.编写存储过程

存储过程的代码为:

  1. USE [TestInsert]  
  2.  
  3. GO  
  4. /****** Object: StoredProcedure [dbo].[CreatePassportWithTVP] Script Date: 03/02/2010 00:14:45 ******/  
  5. SET ANSI_NULLS ON  
  6. GO  
  7. SET QUOTED_IDENTIFIER ON  
  8. GO  
  9. -- =============================================  
  10. -- Author:  <Kevin>  
  11. -- Create date: <2010-3-1>  
  12. -- Description: <创建通行证>  
  13. -- =============================================  
  14. Create PROCEDURE [dbo].[CreatePassportWithTVP]   
  15.  
  16. @TVP PassportTableType readonly  
  17.  
  18. AS  
  19. BEGIN  
  20. SET NOCOUNT ON;  
  21.  
  22. Insert into Passport(PassportKey) select PassportKey from @TVP  
  23.  
  24. END 

可能在查询分析器中,智能提示会提示表值类型有问题,会出现红色下划线(见下图),不用理会,
继续运行我们的代码,完成存储过程的创建

4.编写代码调用存储过程。

三种数据库的插入方式代码如下,由于时间比较紧,代码可能不那么易读,特别代码我加了些注释。

  1. using System;  
  2. using System.Diagnostics;  
  3. using System.Data;  
  4. using System.Data.SqlClient;  
  5. using com.DataAccess;  
  6.  
  7. namespace ConsoleAppInsertTest  
  8. {  
  9.     class Program  
  10.     {  
  11.         static string connectionString = SqlHelper.ConnectionStringLocalTransaction;    //数据库连接字符串  
  12.         static int count = 1000000;           //插入的条数  
  13.         static void Main(string args)  
  14.         {  
  15.             //long commonInsertRunTime = CommonInsert();  
  16.             //Console.WriteLine(string.Format("普通方式插入{1}条数据所用的时间是{0}毫秒", commonInsertRunTime, count));  
  17.  
  18.             long sqlBulkCopyInsertRunTime = SqlBulkCopyInsert();  
  19.             Console.WriteLine(string.Format("使用SqlBulkCopy插入{1}条数据所用的时间是{0}毫秒", sqlBulkCopyInsertRunTime, count));  
  20.  
  21.             long TVPInsertRunTime = TVPInsert();  
  22.             Console.WriteLine(string.Format("使用表值方式(TVP)插入{1}条数据所用的时间是{0}毫秒", TVPInsertRunTime, count));  
  23.         }  
  24.  
  25.         /// <summary>  
  26.         /// 普通调用存储过程插入数据  
  27.         /// </summary>  
  28.         /// <returns></returns>  
  29.         private static long CommonInsert()  
  30.         {  
  31.             Stopwatch stopwatch = new Stopwatch();  
  32.             stopwatch.Start();  
  33.               
  34.             string passportKey;  
  35.             for (int i = 0; i < count; i++)  
  36.             {  
  37.                 passportKey = Guid.NewGuid().ToString();  
  38.                 SqlParameter sqlParameter = { new SqlParameter("@passport", passportKey) };  
  39.                 SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "CreatePassport", sqlParameter);  
  40.             }  
  41.             stopwatch.Stop();  
  42.             return stopwatch.ElapsedMilliseconds;  
  43.         }  
  44.  
  45.         /// <summary>  
  46.         /// 使用SqlBulkCopy方式插入数据  
  47.         /// </summary>  
  48.         /// <param name="dataTable"></param>  
  49.         /// <returns></returns>  
  50.         private static long SqlBulkCopyInsert()  
  51.         {  
  52.             Stopwatch stopwatch = new Stopwatch();  
  53.             stopwatch.Start();  
  54.  
  55.             DataTable dataTable = GetTableSchema();  
  56.             string passportKey;  
  57.             for (int i = 0; i < count; i++)  
  58.             {  
  59.                 passportKey = Guid.NewGuid().ToString();  
  60.                 DataRow dataRow = dataTable.NewRow();  
  61.                 dataRow[0] = passportKey;  
  62.                 dataTable.Rows.Add(dataRow);  
  63.             }  
  64.  
  65.             SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(connectionString);  
  66.             sqlBulkCopy.DestinationTableName = "Passport";  
  67.             sqlBulkCopy.BatchSize = dataTable.Rows.Count;  
  68.             SqlConnection sqlConnection = new SqlConnection(connectionString);  
  69.             sqlConnection.Open();  
  70.             if (dataTable!=null && dataTable.Rows.Count!=0)  
  71.             {  
  72.                 sqlBulkCopy.WriteToServer(dataTable);  
  73.             }  
  74.             sqlBulkCopy.Close();  
  75.             sqlConnection.Close();  
  76.  
  77.             stopwatch.Stop();  
  78.             return stopwatch.ElapsedMilliseconds;  
  79.         }  
  80.  
  81.         private static long TVPInsert()  
  82.         {  
  83.             Stopwatch stopwatch = new Stopwatch();  
  84.             stopwatch.Start();  
  85.  
  86.             DataTable dataTable = GetTableSchema();  
  87.             string passportKey;  
  88.             for (int i = 0; i < count; i++)  
  89.             {  
  90.                 passportKey = Guid.NewGuid().ToString();  
  91.                 DataRow dataRow = dataTable.NewRow();  
  92.                 dataRow[0] = passportKey;  
  93.                 dataTable.Rows.Add(dataRow);  
  94.             }  
  95.  
  96.             SqlParameter sqlParameter = { new SqlParameter("@TVP", dataTable) };  
  97.             SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "CreatePassportWithTVP", sqlParameter);  
  98.  
  99.             stopwatch.Stop();  
  100.             return stopwatch.ElapsedMilliseconds;  
  101.         }  
  102.  
  103.         private static DataTable GetTableSchema()  
  104.         {  
  105.             DataTable dataTable = new DataTable();  
  106.             dataTable.Columns.AddRange(new DataColumn { new DataColumn("PassportKey") });  
  107.               
  108.             return dataTable;  
  109.         }  
  110.  
  111.     }  

比较神秘的代码其实就下面这两行,该代码是将一个dataTable做为参数传给了我们的存储过程。简单吧。

  1. SqlParameter sqlParameter = { new SqlParameter("@TVP", dataTable) };  
  2.  
  3. SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "CreatePassportWithTVP", sqlParameter); 

5.测试并记录测试结果
第一组测试,插入记录数1000

第二组测试,插入记录数10000

第三组测试,插入记录数1000000

通过以上测试方案,不难发现,技术方案二的优势还是蛮高的。无论是从通用性还是从性能上考虑,都应该是
优先被选择的,还有一点,它的技术复杂度要比技术方案三要简单一些,

设想我们把所有表都创建一遍表值类型,工作量还是有的。因此,我依然坚持我开始时的决定,
向公司推荐使用第二种技术方案。

写到此,本文就算完了,但是对新技术的钻研仍然还在不断继续。要做的东西还是挺多的。

设为首页 | 加入收藏 | 网学首页 | 原创论文 | 计算机原创
版权所有 网学网 [Myeducs.cn] 您电脑的分辨率是 像素
Copyright 2008-2020 myeducs.Cn www.myeducs.Cn All Rights Reserved 湘ICP备09003080号 常年法律顾问:王律师