SQLServer批量插入数据的三种方式及性能对比

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

公司技术背景:数据库访问类(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 ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	<Kevin>
-- Create date: <2010-3-1>
-- Description:	<创建通行证>
-- =============================================
Create PROCEDURE [dbo].[CreatePassportWithTVP] 

@TVP PassportTableType readonly

AS
BEGIN
SET 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

通过以上测试方案,不难发现,技术方案二的优势还是蛮高的。无论是从通用性还是从性能上考虑,都应该是优先被选择的,还有一点,它的技术复杂度要比技术方案三要简单一些,设想我们把所有表都创建一遍表值类型,工作量还是有的。因此,我依然坚持我开始时的决定,向公司推荐使用第二种技术方案。

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

为了方便大家学习和交流,代码文件已经打包并上传了,欢迎共同学习探讨。

代码下载

到此这篇关于SQLServer批量插入数据的三种方式及性能对比的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • .NET多种数据库大数据批量插入、更新(支持SqlServer、MySql、PgSql和Oracle)

    功能介绍 (需要版本5.0.45) 大数据操作ORM性能瓶颈在实体转换上面,并且不能使用常规的Sql去实现 当列越多转换越慢,SqlSugar将转换性能做到极致,并且采用数据库最佳API 操作数据库达到极限性能 功能用法 BulkCopy性能远强于现有市场的 ORM框架,比 EFCore Efcore.Bulkextension快30% BulkUpdate吊打现有所有框架是EFCoreEfcore.Bulkextension 2-3倍之快 //插入 100万 10秒不到 db.Fastest<

  • c#几种数据库的大数据批量插入(SqlServer、Oracle、SQLite和MySql)

    在之前只知道SqlServer支持数据批量插入,殊不知道Oracle.SQLite和MySql也是支持的,不过Oracle需要使用Orace.DataAccess驱动,今天就贴出几种数据库的批量插入解决方法. 首先说一下,IProvider里有一个用于实现批量插入的插件服务接口IBatcherProvider,此接口在前一篇文章中已经提到过了. /// <summary> /// 提供数据批量处理的方法. /// </summary> public interface IBatch

  • 详解C#批量插入数据到Sqlserver中的四种方式

    本篇,我将来讲解一下在Sqlserver中批量插入数据. 先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生成一个GUID算法所花的时间肯定比你从数据表中重新查询上一条记录的ID的值然后再进行加1运算要少.而如果存在索引的情况下,每次插入记录都会进行索引重建,这是非常耗性能的.如果表中无可避免的存在索引,我们可以通过先删除索引,然后批量插入,最后再重建索引的方式来提高效率. create database C

  • C#批量插入数据到Sqlserver中的三种方式

    本篇,我将来讲解一下在Sqlserver中批量插入数据. 先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生成一个GUID算法所花的时间肯定比你从数据表中重新查询上一条记录的ID的值然后再进行加1运算要少.而如果存在索引的情况下,每次插入记录都会进行索引重建,这是非常耗性能的.如果表中无可避免的存在索引,我们可以通过先删除索引,然后批量插入,最后再重建索引的方式来提高效率. create database C

  • SQLServer 批量插入数据的两种方法

    运行下面的脚本,建立测试数据库和表值参数. 复制代码 代码如下: --Create DataBase create database BulkTestDB; go use BulkTestDB; go --Create Table Create table BulkTestTable( Id int primary key, UserName nvarchar(32), Pwd varchar(16)) go --Create Table Valued CREATE TYPE BulkUdt A

  • SQLServer批量插入数据的三种方式及性能对比

    昨天下午快下班的时候,无意中听到公司两位同事在探讨批量向数据库插入数据的性能优化问题,顿时来了兴趣,把自己的想法向两位同事说了一下,于是有了本文. 公司技术背景:数据库访问类(xxx.DataBase.Dll)调用存储过程实现数据库的访问. 技术方案一: 压缩时间下程序员写出的第一个版本,仅仅为了完成任务,没有从程序上做任何优化,实现方式是利用数据库访问类调用存储过程,利用循环逐条插入.很明显,这种方式效率并不高,于是有了前面的两位同事讨论效率低的问题. 技术方案二: 由于是考虑到大数据量的批量

  • Oracle批量插入数据的三种方式【推荐】

    第一种: begin insert into tableName(column1, column2, column3...) values(value1,value2,value3...); insert into tableName(column1, column2, column3...) values(value1,value2,value3...); insert into tableName(column1, column2, column3...) values(value1,val

  • MyBatis批量插入数据的三种方法实例

    目录 前言 准备工作 1.循环单次插入 2.MP 批量插入 ① 控制器实现 ② 业务逻辑层实现 ③ 数据持久层实现 MP 性能测试 MP 源码分析 3.原生批量插入 ① 业务逻辑层扩展 ② 数据持久层扩展 ③ 添加 UserMapper.xml 原生批量插入性能测试 缺点分析 解决方案 总结 前言 批量插入功能是我们日常工作中比较常见的业务功能之一,之前我也写过一篇关于<MyBatis Plus 批量数据插入功能,yyds!>的文章,但评论区的反馈不是很好,主要有两个问题:第一,对 MyBat

  • 详解C#实例化对象的三种方式及性能对比

    前言 做项目过程中有个需求要实例化两万个对象并添加到List 中,这个过程大概需要1min才能加载完(传参较多),于是开启了代码优化之旅,再此记录. 首先想到的是可能实例化比较耗时,于是开始对每种实例化方式进行测试,过程如下 实例化方式 1.用 New 关键字实例化一个类 2.用 Activator 实例化一个类 3.用 Assembly 实例化一个类 代码实现 测试环境: vs2019 .NET Framework 4.7 Intel Core i7-10510U CPU 首先定义一个类Per

  • MongoDB中优雅删除大量数据的三种方式

    目录 为什么要"瘦身"呢? MongoDB中删除数据的三种方式 三种方式的执行效率对比 1. remove 2. deleteMany 3. bulkWrite 通过 Write Concern 规避主从延迟 删除过程中遇到的Bug 总结 删除大量数据,无论是在哪种数据库中,都是一个普遍性的需求.除了正常的业务需求,我们需要通过这种方式来为数据库"瘦身". 为什么要"瘦身"呢? 1.表的数据量到达一定量级后,数据量越大,表的查询性能会越差. 毕竟

  • SpringMVC返回json数据的三种方式

    Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面.Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块.使用 Spring 可插入的 MVC架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1,Struts2等. 1.第一种方式是spring2时代的产物,也就是每个json视图controller配置一个Jsoniew. 如:<bean

  • 防止MySQL重复插入数据的三种方法

    新建表格 CREATE TABLE `person` ( `id` int NOT NULL COMMENT '主键', `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '姓名', `age` int NULL DEFAULT NULL COMMENT '年龄', `address` varchar(512) CHARACTER SET utf8 COLLATE utf8_bin N

  • sqlServer实现分页查询的三种方式

    目录 一.offset /fetch next关键字 二.利用max(主键) 三.利用row_number关键字 总结 sqlServer的分页查询和mysql语句不一样,有三种实现方式.分别是:offset /fetch next.利用max(主键).利用row_number关键字 一.offset /fetch next关键字 2012版本及以上才有,SQL server公司升级后推出的新方法. 公式: -- 分页查询公式-offset /fetch next select * from 表

  • iOS开发中UIWebView的加载本地数据的三种方式

    UIWebView是IOS内置的浏览器,可以浏览网页,打开文档 html/htm pdf docx txt等格式的文件. safari浏览器就是通过UIWebView做的. 服务器将MIME的标识符等放入传送的数据中告诉浏览器使用那种插件读取相关文件. uiwebview加载各种本地文件(通过loadData方法): UIWebView加载内容的三种方式: 1 加载本地数据文件 指定文件的MIMEType 编码格式使用@"UTF-8" 2加载html字符串(可以加载全部或者部分html

随机推荐