创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引。GUID必然是比自增长要快。而如果存在索引的情况下,每次插入记录都会进行索引重建,这是非常耗性能的。如果表中无可避免的存在索引,我们可以通过先删除索引,然后批量插入,最后再重建索引的方式来提高效率。

create database CarSYS;
go
use CarSYS;
go
CREATE TABLE Product(
Id UNIQUEIDENTIFIER PRIMARY KEY,
NAME VARCHAR(50) NOT NULL,
Price DECIMAL(18,2) NOT NULL
)

  通过SQL脚本来插入数据,常见如下四种方式。

  一、通过 INSERT INTO tname (...) VALUES (...) 逐条插入,性能最差,不建议使用。

  示例:

#region 方式一
static void InsertOne()
{
Console.WriteLine("采用一条一条插入的方式实现");
Stopwatch sw = new Stopwatch(); using (SqlConnection conn = new SqlConnection(StrConnMsg)) //using中会自动Open和Close 连接。
{
string sql = "INSERT INTO Product(Id,Name,Price) VALUES(newid(),@p,@d)";
conn.Open();
for (int i = ; i < totalRow; i++)
{
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@p", "商品" + i);
cmd.Parameters.AddWithValue("@d", i);
sw.Start();
cmd.ExecuteNonQuery();
Console.WriteLine(string.Format("插入一条记录,已耗时{0}毫秒", sw.ElapsedMilliseconds));
}
if (i == getRow)
{
sw.Stop();
break;
}
}
}
Console.WriteLine(string.Format("插入{0}条记录,每{4}条的插入时间是{1}毫秒,预估总得插入时间是{2}毫秒,{3}分钟",
totalRow, sw.ElapsedMilliseconds, ((sw.ElapsedMilliseconds / getRow) * totalRow), GetMinute((sw.ElapsedMilliseconds / getRow * totalRow)), getRow));
}
static int GetMinute(long l)
{
return (Int32)l / ;
}
#endregion

  运行结果如下:

  

  结果显示插入100w条记录,预计需要50分钟时间,每插入一条记录大概需要3毫秒左右。

  二、使用 insert bulk

BULK INSERT [ [ 'database_name'.][ 'owner' ].]{ 'table_name' FROM 'data_file' }
WITH (
[ BATCHSIZE [ = batch_size ] ],
[ CHECK_CONSTRAINTS ],
[ CODEPAGE [ = 'ACP' | 'OEM' | 'RAW' | 'code_page' ] ],
[ DATAFILETYPE [ = 'char' | 'native'| 'widechar' | 'widenative' ] ],
[ FIELDTERMINATOR [ = 'field_terminator' ] ],
[ FIRSTROW [ = first_row ] ],
[ FIRE_TRIGGERS ],
[ FORMATFILE = 'format_file_path' ],
[ KEEPIDENTITY ],
[ KEEPNULLS ],
[ KILOBYTES_PER_BATCH [ = kilobytes_per_batch ] ],
[ LASTROW [ = last_row ] ],
[ MAXERRORS [ = max_errors ] ],
[ ORDER ( { column [ ASC | DESC ] } [ ,...n ] ) ],
[ ROWS_PER_BATCH [ = rows_per_batch ] ],
[ ROWTERMINATOR [ = 'row_terminator' ] ],
[ TABLOCK ],
)

  相关参数说明:

BULK INSERT
[ database_name . [ schema_name ] . | schema_name . ] [ table_name | view_name ]
FROM 'data_file'
[ WITH
(
[ [ , ] BATCHSIZE = batch_size ] --BATCHSIZE指令来设置在单个事务中可以插入到表中的记录的数量
[ [ , ] CHECK_CONSTRAINTS ] --指定在大容量导入操作期间,必须检查所有对目标表或视图的约束。若没有 CHECK_CONSTRAINTS 选项,则所有 CHECK 和 FOREIGN KEY 约束都将被忽略,并且在此操作之后表的约束将标记为不可信。
[ [ , ] CODEPAGE = { 'ACP' | 'OEM' | 'RAW' | 'code_page' } ] --指定该数据文件中数据的代码页
[ [ , ] DATAFILETYPE =
{ 'char' | 'native'| 'widechar' | 'widenative' } ] --指定 BULK INSERT 使用指定的数据文件类型值执行导入操作。
[ [ , ] FIELDTERMINATOR = 'field_terminator' ] --标识分隔内容的符号
[ [ , ] FIRSTROW = first_row ] --指定要加载的第一行的行号。默认值是指定数据文件中的第一行
[ [ , ] FIRE_TRIGGERS ] --是否启动触发器
[ [ , ] FORMATFILE = 'format_file_path' ]
[ [ , ] KEEPIDENTITY ] --指定导入数据文件中的标识值用于标识列
[ [ , ] KEEPNULLS ] --指定在大容量导入操作期间空列应保留一个空值,而不插入用于列的任何默认值
[ [ , ] KILOBYTES_PER_BATCH = kilobytes_per_batch ]
[ [ , ] LASTROW = last_row ] --指定要加载的最后一行的行号
[ [ , ] MAXERRORS = max_errors ] --指定允许在数据中出现的最多语法错误数,超过该数量后将取消大容量导入操作。
[ [ , ] ORDER ( { column [ ASC | DESC ] } [ ,...n ] ) ] --指定数据文件中的数据如何排序
[ [ , ] ROWS_PER_BATCH = rows_per_batch ]
[ [ , ] ROWTERMINATOR = 'row_terminator' ] --标识分隔行的符号
[ [ , ] TABLOCK ] --指定为大容量导入操作持续时间获取一个表级锁
[ [ , ] ERRORFILE = 'file_name' ] --指定用于收集格式有误且不能转换为 OLE DB 行集的行的文件。
)]

  

#region 方式二
static void InsertTwo()
{
Console.WriteLine("使用Bulk插入的实现方式");
Stopwatch sw = new Stopwatch();
DataTable dt = GetTableSchema(); using (SqlConnection conn = new SqlConnection(StrConnMsg))
{
SqlBulkCopy bulkCopy = new SqlBulkCopy(conn);
bulkCopy.DestinationTableName = "Product";
bulkCopy.BatchSize = dt.Rows.Count;
conn.Open();
sw.Start(); for (int i = ; i < totalRow;i++ )
{
DataRow dr = dt.NewRow();
dr[] = Guid.NewGuid();
dr[] = string.Format("商品", i);
dr[] = (decimal)i;
dt.Rows.Add(dr);
}
if (dt != null && dt.Rows.Count != )
{
bulkCopy.WriteToServer(dt);
sw.Stop();
}
Console.WriteLine(string.Format("插入{0}条记录共花费{1}毫秒,{2}分钟", totalRow, sw.ElapsedMilliseconds, GetMinute(sw.ElapsedMilliseconds)));
}
}
static DataTable GetTableSchema()
{
DataTable dt = new DataTable();
dt.Columns.AddRange(new DataColumn[] {
new DataColumn("Id",typeof(Guid)),
new DataColumn("Name",typeof(string)),
new DataColumn("Price",typeof(decimal))});
return dt;
}
#endregion

  运行结果如下:

  

  插入100w条记录才8s多,推荐。

  打开Sqlserver Profiler跟踪,会发现执行的是如下语句:

insert bulk Product ([Id] UniqueIdentifier, [NAME] VarChar(50) COLLATE Chinese_PRC_CI_AS, [Price] Decimal(18,2))

  三、使用 INSERT INTO xx select...

INSERT INTO Product(Id,Name,Price)
SELECT NEWID(),'牛栏1段',160
UNION ALL
SELECT NEWID(),'牛栏2段',180
UNION ALL
......

  从sqlserver 2008起开始支持TVPs。创建缓存表ProductTemp ,执行如下SQL:

CREATE TYPE ProductTemp AS  TABLE(
Id UNIQUEIDENTIFIER PRIMARY KEY,
NAME VARCHAR(50) NOT NULL,
Price DECIMAL(18,2) NOT NULL
)

  执行完成之后,会发现在数据库CarSYS下面多了一张缓存表ProductTemp

  

  

  可见插入100w条记录共花费了11秒多,此方式麻烦,一次性可使用。  

  四、拼接SQL

INSERT INTO Product(Id,Name,Price) VALUES
(newid(),'牛栏1段',160)
,(newid(),'牛栏2段',260)
......

  示例:此种方法在C#中有限制,一次性只能批量插入1000条,所以就得分段进行插入。

#region 方式四
static void InsertFour()
{
Console.WriteLine("采用拼接批量SQL插入的方式实现");
Stopwatch sw = new Stopwatch(); using (SqlConnection conn = new SqlConnection(StrConnMsg)) //using中会自动Open和Close 连接。
{
conn.Open();
sw.Start();
for (int j = ; j < totalRow / getRow;j++ )
{
StringBuilder sb = new StringBuilder();
sb.Append("INSERT INTO Product(Id,Name,Price) VALUES");
using (SqlCommand cmd = new SqlCommand())
{ for (int i = ; i < getRow; i++)
{
sb.AppendFormat("(newid(),'商品{0}',{0}),", j*i+i);
}
cmd.Connection = conn;
cmd.CommandText = sb.ToString().TrimEnd(',');
cmd.ExecuteNonQuery();
}
}
sw.Stop();
Console.WriteLine(string.Format("插入{0}条记录,共耗时{1}毫秒",totalRow,sw.ElapsedMilliseconds));
}
}
#endregion

  运行结果如下:

  

  可以看到大概花费了10分钟。虽然在方式一的基础上,性能有了较大的提升,但是显然还是不够快。

  总结:大数据批量插入方式一和方式四尽量避免使用而方式二和方式三都是非常高效的批量插入数据方式。其都是通过构建DataTable的方式插入的,而我们知道DataTable是存在内存中的,所以当数据量特别特别大,大到内存中无法一次性存储的时候,可以分段插入。比如需要插入9千万条数据,可以分成9段进行插入,一次插入1千万条。而在for循环中直接进行数据库操作,我们是应该尽量避免的。每一次数据库的连接、打开和关闭都是比较耗时的,虽然在C#中存在数据库连接池,也就是当我们使用using或者conn.Close(),进行释放连接时,其实并没有真正关闭数据库连接,它只是让连接以类似于休眠的方式存在,当再次操作的时候,会从连接池中找一个休眠状态的连接,唤醒它,这样可以有效的提高并发能力,减少连接损耗。而连接池中的连接数,我们都是可以配置的。

C# 之 批量插入数据到 SQLServer 中的更多相关文章

  1. 【转载】C#批量插入数据到Sqlserver中的三种方式

    引用:https://m.jb51.net/show/99543 这篇文章主要为大家详细介绍了C#批量插入数据到Sqlserver中的三种方式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 本篇, ...

  2. C#批量插入数据到Sqlserver中的四种方式

    我的新书ASP.NET MVC企业级实战预计明年2月份出版,感谢大家关注! 本篇,我将来讲解一下在Sqlserver中批量插入数据. 先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的 ...

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

    本篇,我将来讲解一下在Sqlserver中批量插入数据. 先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生 成 ...

  4. C#_批量插入数据到Sqlserver中的四种方式

    先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生成一个GUID算法所花的时间肯定比你从数据表中重新查询上一条记 ...

  5. C#批量插入数据到Sqlserver中的四种方式 - 转

    先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生成一个GUID算法所花的时间肯定比你从数据表中重新查询上一条记 ...

  6. sql 批量插入数据到Sqlserver中 效率较高的方法

    使用SqlBulk #region 方式二 static void InsertTwo() { Console.WriteLine("使用Bulk插入的实现方式"); Stopwa ...

  7. 用SqlBulkCopy批量插入数据到SqlServer数据库表中

    首先创建一个数据库连接类:SQLHelper using System; using System.Collections.Generic; using System.Linq; using Syst ...

  8. .Net批量插入数据到SQLServer数据库,System.Data.SqlClient.SqlBulkCopy类批量插入大数据到数据库

    批量的的数据导入数据库中,尽量少的访问数据库,高性能的对数据库进行存储. 采用SqlBulkCopy来处理存储数据.SqlBulkCopy存储大批量的数据非常的高效,将内存中的数据表直接的一次性的存储 ...

  9. java批量插入数据进数据库中

    方式1: for循环,每一次进行一次插入数据. 方式2: jdbc的preparedStatement的batch操作 PreparedStatement.addBatch(); ...... Pre ...

随机推荐

  1. Spring(六)Spring执行流程

    Spring MVC工作流程图 Spring工作流程描述 1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获: 2. DispatcherS ...

  2. dp经典问题-最大连续子序列和 hdu1003

    题目描述: 这道题我先后做过三遍,结果每一遍都没有做出来.今天再仔仔细细的研究了一下,才发现用动态规划更好理解. 关于求最大连续子序列和的博文转载如下:https://www.cnblogs.com/ ...

  3. TF:TF下CNN实现mnist数据集预测 96%采用placeholder用法+2层C及其max_pool法+隐藏层dropout法+输出层softmax法+目标函数cross_entropy法+AdamOptimizer算法

    import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # number 1 to 10 ...

  4. 二叉搜索树的java实现

    转载请注明出处 一.概念 二叉搜索树也成二叉排序树,它有这么一个特点,某个节点,若其有两个子节点,则一定满足,左子节点值一定小于该节点值,右子节点值一定大于该节点值,对于非基本类型的比较,可以实现Co ...

  5. Java -- 内部类(一)

    什么是内部类 将一个类的定义放在另一个类的定义内部,这就是内部类.在Java中内部类主要分为成员内部类.局部内部类.匿名内部类.静态内部类.举个栗子: public class A { public ...

  6. ES6快速入门(二)数据结构

    ES6快速入门 一.解构 1. 对象解构 let person = { name: 'Tang', age: 28 }; //必须同名,必须初始化 let {name, age} = person; ...

  7. JavaScript基础笔记(十)表单脚本

    表单脚本 一.表单基础知识 JavaScript中表单对应的是HTMLFormElement类型,该类型继承自HTMLElement类型. 通过document.forms可以获得所有表单元素,通过数 ...

  8. [蓝点ZigBee] Zstack 之按键驱动以及控制LED灯 ZigBee/CC2530 视频资料

    这一节主要演示如何在Zstack 下根据板子的不同修改按键驱动,实际演示的时候代码跳动比较多,建议大家除了看视频资料以外,还需要在网上找一下相关资料进一步学习. 视频总览:http://bphero. ...

  9. 潭州课堂25班:Ph201805201 django框架 第五课 自定义简单标签,包含标签,模型类创建,梳理类创建 (课堂笔记)

    自定义标签同自定义过渡器一样,要创建文件,在配置文件中以APP方法注册,对方法进注册,在 html 文件中引入,.. 由模板传参 在 在配置文件中改时区: 由视图函数传参 包含标签: 当有这种重复的代 ...

  10. [NOIP2018]普及组游记

    想不到自己还有机会写游记 ——sysky 考完一个月后 DAY -INF 报名 还为了拍照下载了一个PS 特地把自己P白了一点233 花里胡哨得提交了rg.noi.cn DAY -14~-2 停课集训 ...