SQL批量插入数据几种方案的性能详细对比
昨天下午快下班的时候,无意中听到公司两位同事在探讨批量向数据库插入数据的性能优化问题,顿时来了兴趣,把自己的想法向两位同事说了一下,于是有了本文。
公司技术背景:数据库访问类(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)<br> )
执行成功以后,我们打开企业管理器,按顺序依次展开下列节点--数据库、展开可编程性、类型、用户自定义表类型,就可以看到我们创建好的表值类型了如下图所示:
说明我们创建表值类型成功了。
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:
-- Create date:
-- 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));
}
///
/// 普通调用存储过程插入数据
///
///
private static long CommonInsert()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
string passportKey;
for (int i = 0; i {
passportKey = Guid.NewGuid().ToString();
SqlParameter[] sqlParameter = { new SqlParameter("@passport", passportKey) };
SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "CreatePassport", sqlParameter);
}
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
///
/// 使用SqlBulkCopy方式插入数据
///
///
///
private static long SqlBulkCopyInsert()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
DataTable dataTable = GetTableSchema();
string passportKey;
for (int i = 0; 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 {
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 = { SqlParameter("", dataTable) };<br> SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, "", sqlParameter);
5.测试并记录测试结果
第一组测试,插入记录数1000<br><br>
第二组测试,插入记录数10000<br><br>
第三组测试,插入记录数1000000<br><br>
通过以上测试方案,不难发现,技术方案二的优势还是蛮高的。无论是从通用性还是从性能上考虑,都应该是
优先被选择的,还有一点,它的技术复杂度要比技术方案三要简单一些,
设想我们把所有表都创建一遍表值类型,工作量还是有的。因此,我依然坚持我开始时的决定,
向公司推荐使用第二种技术方案。
写到此,本文就算完了,但是对新技术的钻研仍然还在不断继续。要做的东西还是挺多的。
为了方便大家学习和交流,代码文件已经打包并上传了,欢迎共同学习探讨。
作者:深山老林
出处:http://wlb.cnblogs.com/

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

HQL和SQL在Hibernate框架中进行比较:HQL(1.面向对象语法,2.数据库无关的查询,3.类型安全),而SQL直接操作数据库(1.与数据库无关的标准,2.可执行复杂查询和数据操作)。

《OracleSQL中除法运算的用法》在OracleSQL中,除法运算是常见的数学运算之一。在数据查询和处理过程中,除法运算可以帮助我们计算字段之间的比例或者得出特定数值的逻辑关系。本文将介绍OracleSQL中除法运算的用法,并提供具体的代码示例。一、OracleSQL中除法运算的两种方式在OracleSQL中,除法运算可以使用两种不同的方式进行

在当前智能手机市场上,处理器可谓是各大手机厂商争夺的焦点之一。作为手机的“大脑”,处理器的性能直接影响到手机的运行速度、多任务处理能力以及游戏体验等方面。目前,麒麟8000处理器作为华为旗舰手机的配备,备受关注。那么,它与骁龙处理器相比,究竟有何优劣呢?首先,从性能方面来看,麒麟8000处理器采用了最新的台积电5nm工艺制造,拥有更加先进的制程技术,提升了能

Oracle和DB2是两个常用的关系型数据库管理系统,它们都有自己独特的SQL语法和特点。本文将针对Oracle和DB2的SQL语法进行比较与区别,并提供具体的代码示例。数据库连接在Oracle中,使用以下语句连接数据库:CONNECTusername/password@database而在DB2中,连接数据库的语句如下:CONNECTTOdataba

MyBatis动态SQL标签解读:Set标签用法详解MyBatis是一个优秀的持久层框架,它提供了丰富的动态SQL标签,可以灵活地构建数据库操作语句。其中,Set标签是用于生成UPDATE语句中SET子句的标签,在更新操作中非常常用。本文将详细解读MyBatis中Set标签的用法,以及通过具体的代码示例来演示其功能。什么是Set标签Set标签用于MyBati

SQL中的Identity是什么,需要具体代码示例在SQL中,Identity是一种用于生成自增数字的特殊数据类型,它常用于唯一标识表中的每一行数据。Identity列通常与主键列配合使用,可以确保每条记录都有一个独一无二的标识符。本文将详细介绍Identity的使用方式以及一些实际的代码示例。Identity的基本使用方式在创建表时,可以使用Identit

vivoX100和vivoX100Pro性能对比分析随着智能手机市场的竞争日益激烈,各大品牌不断推出新款产品来满足消费者需求。vivo作为一家备受关注的手机品牌,近年来推出了众多备受好评的产品,其中vivoX100和vivoX100Pro是备受期待的新品。两款手机在外观设计、性能配置、拍照功能等方面有何不同?今天我们就来对比分析vivoX100

解决办法:1、检查登录用户是否具有足够的权限来访问或操作该数据库,确保该用户具有正确的权限;2、检查SQL Server服务的帐户是否具有访问指定文件或文件夹的权限,确保该帐户具有足够的权限来读取和写入该文件或文件夹;3、检查指定的数据库文件是否已被其他进程打开或锁定,尝试关闭或释放该文件,并重新运行查询;4、尝试以管理员身份运行Management Studio等等。
