转自: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 readonlyASBEGINSET NOCOUNT ON;Insert into Passport(PassportKey) select PassportKey from @TVPEND
可能在查询分析器中,智能提示会提示表值类型有问题,会出现红色下划线(见下图),不用理会,继续运行我们的代码,完成存储过程的创建
 

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

SQLServer中批量插入数据方式的性能对比 (转)相关推荐

  1. SQLServer中批量插入数据方式的性能对比

    昨天下午快下班的时候,无意中听到公司两位同事在探讨批量向数据库插入数据的性能优化问题,顿时来了兴趣,把自己的想法向两位同事说了一下,于是有了本文. 公司技术背景:数据库访问类(xxx.DataBase ...

  2. sql中批量插入数据用法

    单次插入数据方法 INSERT INTO 表名(字段1,字段2,字段3)VALUES(第一个值,第二个值,第三个值) 多次插入方法 第一种 INSERT INTO 表名(字段1,字段2,字段3) VA ...

  3. django mysql 一对多_请教,django中 如何向带有外键(一对多和多对多)数据库中批量插入数据?...

    已自行解决,代码如下: json格式:[ { "标题": "小武", "内容": "测试", "类型" ...

  4. MySQL中批量插入数据

    不管怎么样, 你需要大量的数据, 那么问题来了, 怎么快速地插入呢? 1. 这是我创建的一个批量插入的存储过程- 当然, 你可以把参数去掉, 一次性插入1W, 10W- CREATE DEFINER= ...

  5. c向文件中插入数据_Redis从文件中批量插入数据

    简介 在redis中,有时候需要批量执行某些命令,但是在redis的redis-cli下,只能一条条的执行指令,实在太麻烦了! 想到这,你是不是蓝瘦香菇? 如果能将要执行的指令一行行存储到文件中,然后 ...

  6. Mybatis 向指定表中批量插入数据

    UserMapper.xml的方法 <insert id="insertUser" parameterType="java.util.Map">in ...

  7. mysql存储过程--往表中批量插入数据

    为什么80%的码农都做不了架构师?>>>    1.创建表: create test_a( id int); 2.创建存储过程 delimiter $$ create procedu ...

  8. 向数据库中批量插入数据的sql 语句

    declare @count int declare @total int set @count=1; set @total=20; while (@count<=@total) begin i ...

  9. python加数据库_用python批量插入数据到数据库中

    既然使用python操作数据库必不可少的得使用pymysql模块 可使用两种方式进行下载安装: 1.使用pip方式下载安装 pip install pymysql 2.IDE方式 安装完成后就可以正常 ...

最新文章

  1. 张永伟 大数据会成为互联时代重要变革
  2. Microsoft的现代数据管理
  3. 腾讯的这款产品下架了
  4. RegCloseKey函数
  5. 用u盘安装mysql,奥维互动地图企业服务器基本环境安装 ——U盘引导安装CentOS 6.5...
  6. ios下js复制到粘贴板_js实现复制到剪贴板功能,兼容所有浏览器
  7. Meta Learning:元学习模型MAML和Reptile详解
  8. java 读写锁_Java 读写锁的实现
  9. Java实现隐藏文件夹
  10. WINTC编译汇编的方法
  11. 使用OpenSSL生成证书
  12. js购物车选中商品实现计算商品总价格
  13. Spark 常用算子详解(转换算子、行动算子、控制算子)
  14. 各大电商平台API接口调用、拼多多API接口获得淘宝商品详情
  15. slam定位学习笔记(七)-g2o学习
  16. python月薪3万_我月薪5000,靠Python搞副业月入3万
  17. windows 上配置 nginx 转发 https
  18. python 爬取微信公众号文章(selenium+webdriver)
  19. go语言01SDK、GoLand开发工具下载和安装
  20. Unity3D-使用custom font字体以及重叠问题

热门文章

  1. vue computed 源码分析
  2. 读书笔记 --- [基础知识点] 小结2
  3. .NET Core TDD 前传: 编写易于测试的代码 -- 全局状态
  4. 在django中使用celery
  5. [设计模式]中介者模式之Events消息传递实现
  6. JDK自带的log-java.util.logging
  7. 苏州飘“彩云” 五年规模破百亿元
  8. HALCON示例程序inner_rectangle1.hdev木板有效区域提取
  9. EPSON 自带CCD图像处理包的典型应用框架
  10. java 跳转action_JS 跳转到指定Action | 学步园