查看: 4848|回复: 7

[转载] 挑战极限 Oracle数据库一秒导百万数据

[复制链接]
论坛徽章:
407
紫蛋头
日期:2012-05-21 10:19:41迷宫蛋
日期:2012-06-06 16:02:49奥运会纪念徽章:足球
日期:2012-06-29 15:30:06奥运会纪念徽章:排球
日期:2012-07-10 21:24:24鲜花蛋
日期:2012-07-16 15:24:59奥运会纪念徽章:拳击
日期:2012-08-07 10:54:50奥运会纪念徽章:羽毛球
日期:2012-08-21 15:55:33奥运会纪念徽章:蹦床
日期:2012-08-21 21:09:51奥运会纪念徽章:篮球
日期:2012-08-24 10:29:11奥运会纪念徽章:体操
日期:2012-09-07 16:40:00
跳转到指定楼层
1#
发表于 2010-9-15 08:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
挑战极限 Oracle数据库一秒导百万数据
2010年09月07日13:42 来源:李鸣的博客 作者:李鸣 编辑:胡铭娅 评论:2条
本文Tag: Oracle .NET c#     【IT168 技术文档】.Net程序中可以通过ODP调用特性,对Oracle数据库进行操作,今天来讲一下数据批量插入的功能,所用技术不高不深,相信很多朋友都接触过,小弟班门弄斧了,呵呵。这篇文章是上篇文章的续集,因为上一次试验的征集结果没有突破4秒的方法,所以这次继续挑战与挖掘新方法,虽然是Oracle,但仍具有一定收藏意义。

  上一次文章中提及的试验: 挑战极限 C#百万数据4秒导入SQL Server

  这个试验是针对SQL SERVER数据库的,宿主环境也是.Net,有兴趣的朋友可以将这两个试验对比一下,为日后工作批量导数提供支持。

  另外,一些朋友对上次试验环境有些异议,认为应该对数据库和服务器做优化或设置,以体现试验最终的时间结果。这个固然会影响试验的时间结果,但考虑到在试验环境中,对数据库优化的标准与优化程度不便统一与定量,试验结果也不易说明其影响源,所以这次试验依然以标准数据库建库后的配置为主,试验所在服务器硬件环境与上次试验保持一致。实验目的在于挖掘、对比宿主程序中的数据批量操作方法。

  ● 普通肉垫式

  什么叫批量插入呢,就是一次性插入一批数据,我们可以把这批数据理解为一个大的数组,而这些全部只通过一个SQL来实现,而在传统方式下,需要调用很多次的SQL才可以完成,这就是著名的“数组绑定”的功能。我们先来看一下传统方式下,插入多行记录的操作方式:

//设置一个数据库的连接串,
string connectStr = "User Id=scott;
Password=tiger;Data Source=";
OracleConnection conn = new OracleConnection(connectStr);
OracleCommand command = new OracleCommand();
command.Connection = conn; conn.Open();
Stopwatch sw = new Stopwatch();
sw.Start();
//通过循环写入大量的数据,这种方法显然是肉垫
for
(int i = 0; i < recc; i++)
{ string sql = "insert into dept values(" + i.ToString() + "," + i.ToString() + "," + i.ToString() + ")";
command.CommandText = sql;
command.ExecuteNonQuery();
}
sw.Stop();
System.Diagnostics.Debug.WriteLine("普通插入:" + recc.ToString() + "所占时间:" + sw.ElapsedMilliseconds.ToString());
  我们先准备好程序,但是先不做时间的测定,因为在后面我们会用多次循环的方式来计算所占用的时间。

  ● 使用ODP特性

  看上面的程序,大家都很熟悉,因为它没有用到任何ODP的特性,而紧接着我们就要来介绍一个神奇的程序了,我们看一下代码,为了更直观,我把所有的注释及说明直接写在代码里:

//设置一个数据库的连接串
string connectStr = "User Id=scott;Password=tiger;
Data Source=";
OracleConnection conn = new OracleConnection(connectStr);
OracleCommand command = new OracleCommand();
command.Connection = conn;
//到此为止,还都是我们熟悉的代码,下面就要开始喽 //这个参数需要指定每次批插入的记录数
command.ArrayBindCount = recc;
//在这个命令行中,用到了参数,参数我们很熟悉,但是这个参数在传值的时候
//用到的是数组,而不是单个的值,这就是它独特的地方 command.CommandText = "insert into dept values(:deptno, :deptname, :loc)";
conn.Open();
//下面定义几个数组,分别表示三个字段,数组的长度由参数直接给出
int[] deptNo = new int[recc];
string[]
dname = new string[recc];
string[] loc = new string[recc];
// 为了传递参数,不可避免的要使用参数,下面会连续定义三个
// 从名称可以直接看出每个参数的含义,不在每个解释了 OracleParameter deptNoParam = new OracleParameter("deptno", OracleDbType.Int32);
deptNoParam.Direction = ParameterDirection.Input;
deptNoParam.Value = deptNo;
command.Parameters.Add(deptNoParam);
OracleParameter deptNameParam = new OracleParameter("deptname", OracleDbType.Varchar2);
deptNameParam.Direction = ParameterDirection.Input;
deptNameParam.Value = dname;
command.Parameters.Add(deptNameParam);
OracleParameter deptLocParam = new OracleParameter("loc", OracleDbType.Varchar2);
deptLocParam.Direction = ParameterDirection.Input;
deptLocParam.Value = loc; command.Parameters.Add(deptLocParam);
Stopwatch sw = new Stopwatch();
sw.Start();
//在下面的循环中,先把数组定义好,而不是像上面那样直接生成SQL
for
(int i = 0; i < recc; i++) { deptNo[i] = i;
dname[i] = i.ToString();
loc[i] = i.ToString();
}
//这个调用将把参数数组传进SQL,同时写入数据库
command.ExecuteNonQuery();
sw.Stop();
System.Diagnostics.Debug.WriteLine("批量插入:" + recc.ToString() + "所占时间:" +sw.ElapsedMilliseconds.ToString());


  以上代码略显冗长,但是加上注释后基本也就表达清楚了。

  好了,到目前为止,两种方式的插入操作程序已经完成,就剩下对比了。我在主函数处写了一个小函数,循环多次对两个方法进行调用,并且同时记录下时间,对比函数如下:

for (int i = 1; i <= 50; i++) { Truncate(); OrdinaryInsert(i * 1000); Truncate(); BatchInsert(i * 1000); }
  
       当数据量达到100万级别时,所用时间依然令人满意,最快一次达到890毫秒,一般为1秒左右。

  经过试验,得出一组数据,可以看出两种方式在效率方面惊人的差距(占用时间的单位为毫秒),部分数据如下:

记录数  标准  批处理  
1000  1545  29  
2000 3514  20  
3000 3749  113  
4000 5737  40  
5000 6820  52  
6000 9469  72  
7000 10226  69  
8000 15280  123  
9000 11475  83  
10000 14536  121  
11000 15705  130  
12000 16548  145  
13000 18765  125  
14000 20393  116  
15000 22181  159  

  因为篇幅原因,不再粘贴全部的数据,但是我们可以看一下由此数据生成的散点图:




  其中有些数据有些跳跃,可能和数据库本身有关系,但是大部分数据已经能说明问题了。看了这些数据后,是不是有些心动了?

  源程序放了一段时间直接拷贝贴过来了,可能需要调试一下才能跑通,不过不是本质性问题,对了如果要测试别忘记安装Oracle访问组件。

[[i] 本帖最后由 〇〇 于 2010-9-15 09:02 编辑 [/i]]
论坛徽章:
407
紫蛋头
日期:2012-05-21 10:19:41迷宫蛋
日期:2012-06-06 16:02:49奥运会纪念徽章:足球
日期:2012-06-29 15:30:06奥运会纪念徽章:排球
日期:2012-07-10 21:24:24鲜花蛋
日期:2012-07-16 15:24:59奥运会纪念徽章:拳击
日期:2012-08-07 10:54:50奥运会纪念徽章:羽毛球
日期:2012-08-21 15:55:33奥运会纪念徽章:蹦床
日期:2012-08-21 21:09:51奥运会纪念徽章:篮球
日期:2012-08-24 10:29:11奥运会纪念徽章:体操
日期:2012-09-07 16:40:00
2#
 楼主| 发表于 2010-9-15 08:51 | 只看该作者
挑战极限 C#百万数据4秒导入SQL Server
2010年09月07日11:12 来源:李鸣的博客 作者:李鸣 编辑:胡铭娅 评论:0条
本文Tag: Sql Server 2000 c# SQL Server SQL Server 2008     【IT168 技术文档】实际工作中有时候需要把大量数据导入数据库,然后用于各种程序计算,本实验将使用5中方法完成这个过程,并详细记录各种方法所耗费的时间。

  本实验中所用到工具为VS2008和SQL SERVER 2000、SQL SERVER 2008,分别使用5中方法将100万条数据导入SQL 2000与SQL 2008中,实验环境是DELL 2850双2.0GCPU,2G内存的服务器。感兴趣的朋友可以下载源代码自己验证一下所用时间。

  还要有一点需要进行说明,本实验中执行SQL语句的地方使用了IsLine FrameWork框架中的DataProvider模块,这个模块只是对SQL配置的读取和封装,并不会对最终结果有本质性的影响,关于IsLine FrameWork框架方面的知识,请参考“IsLine FrameWork”框架系列文章。

  下面进入正题,分别使用基本的Insert 语句、使用BULK INSERT语句、在多线程中使用BULK INSERT、使用SqlBulkCopy类、在多线程中使用SqlBulkCopy类五种方法,挑战4秒极限。

  数据库方面使用SQL 2000与SQL 2008,表名TableB,字段名称为Value1,数据库名可以在App.config中修改,默认为test。


 
 图 1 试验中的5种方法

  方法一.使用基本的Insert 语句

  这种方法是最基本的方法,大多数人一开始都会想到这种方法。但是Insert语句似乎并不适合大批量的操作,是不是这样呢?

  本方法中将100万数据分为10个批次,每个批次10万条,每10万条1个事务,分10次导入数据库。

  基本语句:Insert Into TableB (Value1) values (‘”+i+”’);

  说明:语句中的i是宿主程序中的一个累加变量,用于填充数据库字段中的值。

  SQL 2000 耗时:901599

  SQL 2008耗时:497638

  方法二.使用BULK INSERT语句

  这个类的效果,在本实验中可以说是最令人满意的了,它的使用最简便、灵活,速度很快。

  “BULK INSERT”语句似乎不是很常用, Aicken听说Oracle中有一种可以将外部文件映射为Oracle临时表,然后直接将临时表中的数据导入Oracle其他表中的方法,这种方法的速度非常令人满意,SQL SERVER的BULK INSERT是不是同样令人满意呢?

  基本语句:BULK INSERT TableB FROM 'c:\\sql.txt' WITH (FIELDTERMINATOR = ',',ROWTER /.,mbMINATOR='|',BATCHSIZE = 100000)

  说明:“c:\\sql.txt”是一个预先生成的包含100条数据的文件,这些数据以“|”符号分隔,每10万条数据一个事务。

  SQL 2000耗时:4009

  SQL 2008耗时:10722

  方法三.在多线程中使用BULK INSERT

  在方法二的基础上,将100万条数据分五个线程,每个线程负责20万条数据,每5万条一个事物,五个线程同时启动,看看这样的效果吧。

  SQL 2000耗时:21099

  SQL 2008耗时:10997

  方法四.使用SqlBulkCopy类

  这种方法速度也很快,但是要依赖内存,对于几千万条、多字段的复杂数据,可能在内存方面会有较大的消耗,不过可以使用64位解决方案处理这个问题。

  几千万条、多字段的数据的情况一般在一些业务场景中会遇到,比如计算全球消费者某个业务周期消费额时,要先获得主数据库表中的会员消费记录快照,并将快照储存至临时表中,然后供计算程序使用这些数据。并且有些时候消费者的消费数据并不在一台数据库服务器中,而是来自多个国家的多台服务器,这样我们就必须借助内存或外存设备中转这些数据,然后清洗、合并、检测,最后导入专用表供计算程序使用。

  基本语句:

using (System.Data.SqlClient.SqlBulkCopy sqlBC = new System.Data.SqlClient.SqlBulkCopy(conn))  
          {                sqlBC.BatchSize = 100000;      
          sqlBC.BulkCopyTimeout = 60;      
          sqlBC.DestinationTableName = "dbo.TableB";   
          sqlBC.ColumnMappings.Add("valueA", "Value1");
              sqlBC.WriteToServer(dt);         
   }
  说明:

  BatchSize = 100000; 指示每10万条一个事务并提交

  BulkCopyTimeout = 60; 指示60秒按超时处理

  DestinationTableName = "dbo.TableB"; 指示将数据导入TableB表

  ColumnMappings.Add("valueA", "Value1"); 指示将内存中valueA字段与TableB中的Value1字段匹配

  WriteToServer(dt);写入数据库。其中dt是预先构建好的DataTable,其中包含valueA字段。

  SQL 2000耗时:4989

  SQL 2008耗时:10412

  方法五.在多线程中使用SqlBulkCopy类

  基于方法四,将100万条数据分五个线程,每个线程负责20万条数据,每5万条一个事物,五个线程同时启动,看看这样的效果吧。

  SQL 2000耗时:7682

  SQL 2008耗时:10870

  总结

数据库
测试方式

SQL 2000
  SQL 2008
  
基本Insert Into
  901599
  497638
  
单线程Bulk Insert
  4209
  10722
  
多线程Bulk Insert
  21099
  10997
  
单线程SqlBulkCopy
  4989
  10412
  
多线程SqlBulkCopy
  7682
  10870
  

  以上就是这几天的实验结果了,比较令人失望的是SQL SERVER 2008导入数据的性能似乎并不想我们想象的那样优秀。

使用道具 举报

回复
论坛徽章:
115
生肖徽章:狗
日期:2007-01-06 21:14:12马上有车
日期:2014-03-06 16:45:08马上加薪
日期:2014-05-09 12:27:582014年世界杯参赛球队: 英格兰
日期:2014-07-03 13:10:44青年奥林匹克运动会-竞技体操
日期:2014-09-10 15:30:57马上有钱
日期:2014-10-31 13:56:48美羊羊
日期:2015-03-04 14:48:582015年新春福章
日期:2015-03-06 11:57:31懒羊羊
日期:2015-04-23 19:26:10金牛座
日期:2015-09-17 08:21:44
3#
发表于 2010-9-15 09:14 | 只看该作者
100万记录,有几个字段,平均行长呢?
如果只有1个字段,宽度1,100万才1M

使用道具 举报

回复
论坛徽章:
69
生肖徽章2007版:羊
日期:2008-11-14 14:42:19复活蛋
日期:2011-08-06 08:59:05ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:542012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:20版主4段
日期:2012-05-15 15:24:11
4#
发表于 2010-9-15 09:27 | 只看该作者
原帖由 atgc 于 2010-9-15 09:14 发表
100万记录,有几个字段,平均行长呢?
如果只有1个字段,宽度1,100万才1M

这个问题问得好.

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
5#
发表于 2010-9-15 10:30 | 只看该作者
粗略扫了一下,没发现硬件设备的说明

使用道具 举报

回复
求职 : 系统架构师
论坛徽章:
184
现任管理团队成员
日期:2011-05-07 01:45:082015年新春福章
日期:2015-03-06 11:57:31懒羊羊
日期:2015-03-04 14:48:16马上有车
日期:2015-02-03 15:49:36马上加薪
日期:2014-11-14 21:57:36itpub13周年纪念徽章
日期:2014-09-27 21:15:55马上有钱
日期:2014-06-16 15:55:42马上有房
日期:2014-06-16 15:55:42问答徽章
日期:2014-04-19 09:26:09马上有房
日期:2014-04-01 21:27:12
6#
发表于 2010-9-15 10:56 | 只看该作者
假如clob,blob字段还有这么快?

使用道具 举报

回复
论坛徽章:
181
慢羊羊
日期:2015-03-04 14:19:442015年新春福章
日期:2015-03-06 11:57:31
7#
发表于 2010-9-15 11:05 | 只看该作者
根据表描述来看 "insert into dept values(:deptno, :deptname, :loc)"
三个字段,每个字段最长字节也就10个左右,平均下来估计每条记录15个字节左右

使用道具 举报

回复
论坛徽章:
115
生肖徽章:狗
日期:2007-01-06 21:14:12马上有车
日期:2014-03-06 16:45:08马上加薪
日期:2014-05-09 12:27:582014年世界杯参赛球队: 英格兰
日期:2014-07-03 13:10:44青年奥林匹克运动会-竞技体操
日期:2014-09-10 15:30:57马上有钱
日期:2014-10-31 13:56:48美羊羊
日期:2015-03-04 14:48:582015年新春福章
日期:2015-03-06 11:57:31懒羊羊
日期:2015-04-23 19:26:10金牛座
日期:2015-09-17 08:21:44
8#
发表于 2010-9-15 11:10 | 只看该作者
原帖由 bq_wang 于 2010-9-15 11:05 发表
根据表描述来看 "insert into dept values(:deptno, :deptname, :loc)"
三个字段,每个字段最长字节也就10个左右,平均下来估计每条记录15个字节左右

那就15M,1秒钟应该什么问题
我做过测试,笔记本 2G内存
sqlldr 导入50M ,20万行,直接路径,设置合理的buffer等,3.5~5秒

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表