`

[转] Java BCP 改进

 
阅读更多
转自http://blog.csdn.net/kaopusoft/article/details/7371206

BCP(Bulk CopyProgram批量数据拷贝工具,下同)是MicrosoftSQL Server和Sybase数据库提供的非常实用的数据导入/导出工具。它方便实用,性能卓越。深得程序员的喜爱。

在项目开发过程中,需要在Java应用里用定时任务实现数据的批量导入/导出。常规的做法是用insert into语句直接插入数据,或者调用BCP命令导入数据。但这两种方法都有其固有的局限性,有没有一种更为方便的方法呢?

思路
数据导出功能较易实现。只要用Statement直接执行SQL语句返回ResultSet,然后将查询结果序列化为字符串后写到文本中去。唯一需要注意的是创建Statement时一定要指定游标类型为只读(CONCUR_READ_ONLY)和前向读取(TYPE_FORWARD_ONLY),否则可能造成内存溢出。

难点是在数据导入。

使用insert into语句的方式插入数据,数据库服务器的负荷将迅速增大,日志会迅速增大甚至溢出。在实时业务系统中运用时,必须要在插入若干条后暂停并清理日志,不仅处理效率低,而且可能造成业务中断。

直接调用BCP命令的前提是要安装和配置Sybase或Microsoft SQL Server(后文中均简写为SQL Server)客户端,并且需要根据数据库类型不同,定位到不同的BCP.exe文件路径,在应用程序中也很难得到数据导入的情况。

有一个叫jBCP的开源项目,作者声称可以将数据从文件导入到 SQL Server和Sybase数据库。经过初步测试,jBCP的导入仅仅能适用于Sybase,且要导入的表至少需要满足下列条件:

1.所有字段不能为空;

2.字段值不能包含中文字符;

3.表不能包含tinyint类型的字段;

也就是说,对于BCP功能,jBCP提供的包并不具备商用条件。

尽管如此,jBCP为我们提供了解决问题的基本思路和方法,那就是直接用TDS协议与数据库进行TCP通信,向服务端发送BCP命令和数据。

数据库客户端和服务端的通信并不像我们想象的那样神秘。我们知道,数据库服务也是作为Socket服务端响应客户端连接请求的。例如在缺省状态下,SQL Server通常的端口号是1433,Oracle的端口号是1521,Sybase的端口号是5000。要跟数据库通信,就得先建立Socket连接,然后使用数据库特有的协议进行消息交互。

对于SQL Server和Sybase,它使用的Socket通信协议是TDS(Tabular Data Stream Protocol,表格化数据流协议,下同)。TDS版本和数据库的版本对应关系如下表:

    
TDS 版本

      
支持产品及版本

 
    
4.2

      
Sybase SQL Server < 10 和 Microsoft SQL Server 6.5

 
    
5.0

      
Sybase SQL Server >= 10

 
    
7.0

      
Microsoft SQL Server 7.0

 
    
8.0

      
Microsoft SQL Server 2000

 
    
9.0

      
Microsoft SQL Server 2005

 

由于Sybase10以下版本和Microsoft SQL Server6.5版本目前较少使用,本文中所指的数据库均以TDS5.0以上版本为准,同时也不针对Sybase10以下版本和Microsoft SQL Server6.5做兼容性测试。

BCP 导出功能jBCP包并没有实现。但bcp导出的过程实际上是执行Select * from table,然后把字段数据根据规则写入文件。我们可以用这个方法自行实现。

在TDS协议中,规定了导入的Bulk Copy Packet的格式。一个典型的BCP导入会话过程如下:

--> 建立连接并认证

<-- 认证结果

--> 声明将要进行BCP操作(insert bulk数据库名..表或视图名)

<-- 字段结构说明

--> 结构化数据

--> 数据结束(TDS7.0+, 即SQL Server才需要)

<-- 导入结果

由此可见,BCP导入的过程实际上是数据库服务端先准备好接收BCP数据,然后由客户端发送二进制的字段数据给服务端,服务端将这些数据快速写入数据库。归纳起来,BCP性能高的原因如下:

1.单向发送数据,避免频繁的请求/响应操作,也减少了网络交互包的数量;

2.服务端不处理SQL语句,避免做语法分析、预编译等操作;

3.服务端不做数据类型映射转换;

4.客户端直接向Socket连接写数据流,避免其它多余环节;

5.对于快速BCP,服务端不产生日志,仅记录页分配。

实践
       结合搜集到的BCP资料,综合运用抓包工具、比较工具分析后,笔者修改了jBCP的源代码。经过测试,解决了jBCP不能正常工作的问题。

       笔者使用Ethereal来抓取BCP客户端发送到服务端的数据包,将它与用jBCP发送的数据包用UltraEdit的二进制比较工具进行比较,然后将它与TDS协议文档进行逐字段对应,试图发现导入失败的原因和规律。

       经过反复分析和验证,笔者总结出了TDS协议的BCP请求包结构。

1.     SybaseBCP请求包结构
TDS5.0协议(适用于Sybase10以上)中的BCP请求数据包,可以简单解析如下:

BCP包结构:

TDS包头   8字节

行记录1

行记录2

……

行记录N



              行记录结构:

本行长度    双字节整数,单位为字节

变长列数量  单字节整数

0x00

定长字段1   长度根据字段定义确定

定长字段2   长度根据字段定义确定

……

定长字段N   长度根据字段定义确定

本行长度     双字节整数(与前一个记录长度相同)

变长字段1   长度根据字段实际长度计算

变长字段2   长度根据字段实际长度计算

……

变长字段N   长度根据字段实际长度计算

校正表(可选)  偏移表都是单字节,不够用的时候启用校正表

偏移表       记录变长字段的偏移位置

2.     SQLServer BCP 请求包结构
TDS7.0及以上协议(适用于SQL Server7.0以上)中的BCP请求数据包,可以简单解析如下3:



<PacketHeader>

  <Type>

    <BYTE>07 </BYTE>

  </Type>

  <Status>

    <BYTE>01 </BYTE>

  </Status>

  <Length>

    <BYTE>00 </BYTE>

    <BYTE>26 </BYTE>

  </Length>

  <SPID>

    <BYTE>00 </BYTE>

    <BYTE>00 </BYTE>

  </SPID>

  <Packet>

    <BYTE>01 </BYTE>

  </Packet>

  <Window>

    <BYTE>00 </BYTE>

  </Window>

</PacketHeader>

<PacketData>

  <BulkLoadBCP>

    <COLMETADATA>

      <TokenType>

        <BYTE>81 </BYTE>

      </TokenType>

      <Count>

        <USHORT>01 00 </USHORT>

      </Count>

      <ColumnData>

        <UserType>

          <ULONG>00 00 00 00</ULONG>

        </UserType>

        <Flags>

          <USHORT>05 00 </USHORT>

        </Flags>

        <TYPE_INFO>

          <FIXEDLENTYPE>

            <BYTE>32 </BYTE>           </FIXEDLENTYPE>

        </TYPE_INFO>

        <ColName>

          <B_UNICODE>

            <BYTELEN>

              <BYTE>02 </BYTE>

            </BYTELEN>

            <BYTESascii="c.1.">63 00 31 00 </BYTES>

          </B_UNICODE>

        </ColName>

      </ColumnData>

    </COLMETADATA>

    <ROW>

      <TokenType>

        <BYTE>D1 </BYTE>

      </TokenType>

      <TYPE_VARBYTE>

        <BYTES>00 </BYTES>

      </TYPE_VARBYTE>

    </ROW>

    <DONE>

      <TokenType>

        <BYTE>FD </BYTE>

      </TokenType>

      <Status>

        <USHORT>00 00 </USHORT>

      </Status>

      <CurCmd>

        <USHORT>00 00 </USHORT>

      </CurCmd>

      <DoneRowCount>

        <LONGLONG>00 00 00 00 00 00 00 00</LONGLONG>

      </DoneRowCount>

    </DONE>

  </BulkLoadBCP>

</PacketData>

SQL Server的字段是按照字段定义顺序排列的;在BCP发送之前,要发送字段定义包;批量提交时要发送一个表示提交的DONE结构;记录之间使用0xd1作为分隔符。这与Sybase采用的TDS5.0有显著的不同。

3.     jBCP抓包分析及改进
使用未修改的jBCP包导入数据,抓包得到了一个响应结果如下:



0000  00 14 2a 6f d2 eb 00 d0 d0 c6 d5 01 08 00 45 00 ..*o..........E.

0010  00 fa 65 5c 40 003d 06 40 d1 0a 82 30be 0a 82  ..e\@.=.@...0...

0020  51 0f 10 0408 b3 1d 08 6f d8 8d21 af ff 50 18  Q.......o..!..P.

0030  80 00 a0 0e 00 00 04 01 00d2 00 00 00 00 aa be  ................

0040  00 e5 12 00 00 01 10 ac00 42 61 64 20 72 6f77  .........Bad row

0050  20 64 61 74 61 20 72 65 63 65 69 76 65 64 20 66   data received f

0060  72 6f 6d 2074 68 65 20 63 6c 69 656e 74 20 77  rom the client w

0070  68 69 6c 65 2062 75 6c 6b 20 63 6f 70 79 69 6e  hile bulk copyin

0080  67 20 69 6e 74 6f 20 6f 62 6a 65 63 74 20 31 30  g into object 10

0090  34 39 37 36 37 37 36 36 20 69 6e 20 64 61 74 61  49767766 in data

00a0   62 61 73 65 20 37 2e 20 5265 63 65 69 76 65 64  base 7. Received

00b0  20 61 20 72 6f 77 206f 66 20 6c 65 6e 67 74 68   a row oflength

00c0   20 31 37 32 32 33 20 77 6869 6c 73 74 20 6d 61   17223 whilst ma

00d0  78 69 6d 75 6d 20 6f 72 2065 78 70 65 63 74 65  ximum or expecte

00e0  64 20 72 6f 77 206c 65 6e 67 74 68 20 6973 20  d row length is

00f0   39 38 37 2e 0a 06 53 59 42 41 53 45 00 01 00 fd  987...SYBASE....

0100  12 00 03 00 00 00 00 00                          ........



       大意是说,对于给定表的字段,列的长度超出了允许范围。这说明发送的BCP请求包与服务端要求的请求包格式不一致。

进一步将jBCP的BCP数据包和bcp工具产生的BCP请求数据包进行比较,陆续发现下列问题:

1.  在BCP消息头部的字段定义处,tinyint字段长度是4字节。但实际tinyint是1个字节。

2.  中文字符的长度,jBCP均以字符数计算,没有用实际字节长度;

3.  Microsoft SQL Server的BCP消息,没有按照协议中规定的批量提交(DONE)标识,且消息头部的字段定义是非必选填入的。

找出这些规律后,再分别修改对应的jBCP源代码,然后进行测试。

4.     jBCP改进后的测试结果
测试硬件环境:

客户端:HP  nc6400 笔记本,Inter 双核T2300 1.66G/1G

服务端:SQLServer: TCL台式机 Inter 双核 2.8G/1G

       Sybase: PC Server,Inter 2.4G/2G

测试方法:分别使用SQL Server和Sybase自带的BCP程序做数据导入;用jBCP分别做对应的数据导入。

使用来自于实际生产数据库中的某个日志表作为导入样本。字段数为36个,其中变长字段数为21个,记录数为815,963条,每2000条批量提交一次。

    
导入类型

      
平均速度

      
平均服务端

 
CPU占用

      
平均客户端

 
内存占用

      
平均客户端

 
CPU占用

 
    
jBCP-> SQL Server

      
10427.642

      
18%

      
23524K

      
45%

 
    
BCP-> SQL Server

      
18145.01

      
35%

      
4964K

      
24%

 
    
jBCP-> Sybase

      
3509.983

      
18%

      
24076K

      
16%

 
    
BCP-> Sybase

      
3906.47

      
18%

      
24724K

      
12%

 

从测试结果可以看出,数据库厂商提供的BCP程序性能具有先天的优势,Sybase 的BCP程序较之jBCP优势体现不明显,而SQLServer的BCP程序做了更多优化,其导入速度比jBCP快了1/2到1/3,数据库服务端CPU占用率并不高,性能瓶颈在客户端。而Sybase的BCP优化效率不明显,性能瓶颈在数据库服务器端。

另一方面也再次说明BCP导入方式的优越性,即对数据库服务器的负荷并无大的冲击,几乎可以在实时业务处理数据库中随时进行。

分别使用不同的表结构、带有中文的多个用例做数据导入,全部通过测试。

5.     jBCP的使用方法
jBCP有两种用法:

(1)       一般使用方法

           Class.forName("net.sourceforge.jtds.jdbc.Driver");

           BCPbcp = DriverManager.getConnection("jdbc:jbcp:sybase://ip:port/dbname", userName, password);

           try{

               bcp.config(dataFile,errFile, tableName, batchSize, delimiter);

               bcp.run();

           }

           finally {

               bcp.close();

           }

(2)       高级使用方法

Class.forName("net.sourceforge.jtds.jdbc.Driver");

           BCPbcp = DriverManager.getConnection("jdbc:jbcp:sybase://ip:port/dbname", userName, password);

           try{

               bcp.bcpInit(tableName);

              bcp.bcpSendRow(columnsArray); // columnsArray是一个字符串数组

              ……

bcp.bcpSendRow(columnsArray); // columnsArray是一个字符串数组

              ……

              bcp.bcpBatch();

bcp.bcpSendRow(columnsArray); // columnsArray是一个字符串数组

              ……

              bcp.bcpBatch();

              bcp.bcpDone();             

           }

           finally {

              bcp.close();

}

前一种方法是直接给定数据文件名等参数,适用于给定文件直接导入。后一种方法逐行处理,每次输入一个字符串数组,其中每个数组元素代表一个列的值。
分享到:
评论

相关推荐

    BCP资料BCP资料BCP资料BCP资料BCP资料BCP资料

    BCP资料BCP资料BCP资料BCP资料BCP资料BCP资料BCP资料BCP资料

    求一条生成十六进制的BCP语句

    求一条生成十六进制的BCP语句

    sybase bcp

    sybase bcp工具 sybase bcp工具 sybase bcp工具 sybase bcp工具 sybase bcp工具

    BCP详细参数.txt

    bcp 实用工具在 Microsoft? SQL Server? 2000 实例和数据文件之间以用户指定的格式复制数据。 语法 bcp {[[database_name.][owner].]{table_name | view_name} | "query"} {in | out | queryout | format} data_...

    sybase bcp命令详解

    bcp sybase 数据库 讲解bcp参数以及使用方法

    bcp常见问题处理

    sybase数据库bcp命令说明,bcp常见问题处理

    bcp命令详解+实例

    详细描述了bcp命令的使用. 包括怎么从数据库中bcp出文件, 怎么把文件bcp in到数据库中。

    sql bcp 使用大全

    bcp是sql用于进行大量数据传输/转换的工具。本文档主要针对其使用方法进行了详细的总结,并给出了实际例子,想信会为不了解bcp工具使用的读者以启示。

    浪潮BCP双机安装

    inspur bcp 浪潮 机安装签 service 双机 无共享磁盘双机 浪潮双机

    delphi开发的BCP小工具

    delphi开发的BCP小工具,可以反馈BCP列表及相关错误等等。

    BCP_命令工具学习

    BCP_备份还原工具 批量导入导出的工具

    mikrotik ros的bcp教程

    mikrotik ros的bcp教程,使用ppp封装网桥

    bcp的使用说明

    bcp的使用说明

    BCP命令详解

    BCP 命令详解 BCP 命令是 SQL Server 中负责导入导出数据的一个命令行工具,它是基于 DB-Library 的,并且能以并行的方式高效地导入导出大批量的数据。BCP 命令可以用来将数据从 SQL Server 导出到文本文件中,也...

    bcp 复制工具介绍

    bcp 复制工具介绍bcp命令是SQL Server提供的一个快捷的数据导入导出工具。使用它不需要启动任何图形管理工具就能以高效的方式导入导出数据。

    bcp导入导出数据

    bcp导入导出SQL Server和Sybase数据库数据

    sybase BCP实用工具详解

    sybase BCP实用工具详解,BCP命令详细解释。

    BCP文件对比工具

    该文件用于比较两个BCP文件的每个对象是否一致

    bcp常见问题集合

    用bcp从文本文件导入数据到sybase中,比如: 数据库服务器是mydb 表名是table_name 数据文件是 data_file 用户名是user 密码是passwd 数据库服务名是 db_name 那么完整的写法是: bcp mydb..table_name in ./data_file...

    PPP BCP_patch文件

    PPP BCP功能开发patch,适用用Linux-2.4.21的内核,但是很有参考价值。我开发此功能就是参考这个patch

Global site tag (gtag.js) - Google Analytics