【问题标题】:Best way to download and process a Very Large Compressed MS Access File to be loaded in Oracle下载和处理要在 Oracle 中加载的超大压缩 MS 访问文件的最佳方法
【发布时间】:2013-10-23 20:52:53
【问题描述】:

我必须下载一个 250mb~ 通过 FTP 加密的 ZIP 文件密码。下载后我必须使用通用密码解压缩,Zip 文件将包含一个 1.5GB 的 MS Access 数据库,我必须读取该数据库并与我的数据库 Oracle 中的一些表进行一些连接,并将该数据转换并加载到该 Oracle 数据库中。

我正在寻找执行此过程的最佳方法。 我是ac#开发人员,所以我的第一个想法是使用c#,通过FtpClientFtpWebRequest下载文件,然后使用DotNetZip之类的zip库并通过ODBC打开MS Access数据库并将记录加载到Oracle 与 ODP.NEt,我认为这是我的“简单方法”,因为我知道该怎么做。

但由于这是一个大文件,而且我知道这可能需要很长时间,所以我担心时间和效率以及如何减少此过程的时间。

所以我认为将所有请求直接处理到 oracle(从那里下载 FTP,在那里解压缩,然后直接在那里处理信息会减少像从 c# 到 oracle 的逐条记录那样的时间)应该减少这个过程的时间,但我不确定这是否是正确的方法。

所以我开始研究 oracle 的库,这些库可以做我想做的事情,我找到了PLSQL-utils,似乎他们可以做我需要的一切,除了阅读 MS Access 数据库,我开始寻找并找到了Heterogeneous Services,但我从未使用过它们,所以我对此有点迷茫。

我也听说过我可以直接从 Oracle 使用 Java,而且我知道 java 可以通过 JDBC 连接到 MS Access。所以我搜索了一下,发现了一些关于Calling Java Methods in Oracle Database

到目前为止,这就是我所拥有的,但我不知道应该使用哪种方法,我的意思是,据我所知,RDBMS 用于处理数据,而不是用于编程下载文件或类似的东西,这就是我们拥有 OOP 语言的原因。

作为附加信息,这个过程将在一个月内执行一次或两次,所以我必须安排它,如果它在 oracle 中,可以通过计划作业轻松完成,或者在 c# 中使用计划任务或 Windows服务(这些是我知道的工具)

我有一些限制

  • 我的客户没有 MS SQL Server,也无法为其购买许可证(因此我无法在此过程中使用 DTSX)
  • 在 Oracle 生产服务器中也许我没有足够的权限来做所有的事情,但我可以遵守那些最适合流程的事情
  • 如果需要后端服务器(Java、c# 托管在 IIS 或 WebLogic 或 JBoss 或任何类型),则此服务器和 Oracle 服务器将有所不同
  • 在 Unix 服务器上托管的 Oracle 数据库

说了这么多,我怎样才能有效地完成所有这些过程,我应该使用 .net 并在我的 Oracle 数据库中逐条加载记录吗?我应该在 oracle 中做所有事情吗?还是什么都不做?有没有更好的方法来做到这一点?

【问题讨论】:

  • 你搜索过oracle数据库链接吗?有一种方法可以将 oracle 连接到另一个数据库,看看这个帖子 asktom.oracle.com/pls/asktom/… 有一个指向文档的链接,但是已经坏了,所以你会找到它在哪里。
  • 是的,我认为这是关于我对异构服务的评论,指的是 dblink

标签: c# java asp.net oracle oracle10g


【解决方案1】:

我将创建一个可调用的应用程序或网页或 WCF 服务,这些服务将下载文件、解压缩文件并将记录逐行直接插入 Oracle。我会尽量将所有数据保存在 Oracle 中。得到它并保存它。下载 zip 文件后,我不会乱用它。

基本上:

数据 ->处理器应用程序 ->Oracle

通过这种方式,事情可以自动化。我会用 C# 做处理器。

【讨论】:

    【解决方案2】:

    我认为您在使用 C# 控制台应用程序以使其成为可重复的过程方面走在正确的轨道上。 Here 是一个很棒的免费库,我在许多项目中都使用过 zip。

    using (var client = new WebClient())
    using (var stream = client.OpenRead(@"ftp://mysite.com/mydb.zip"))
    using (var file = File.Create(@"c:\temp\mydb.zip"))
    {
        stream.CopyTo(@"c:\temp\mydb.zip", 32000);
    }
    
    using (ZipFile zip = ZipFile.Read(@"c:\temp\mydb.zip"))
    {
        ZipEntry e = zip["bigdb.mdb"];
        e.Password = "yourpassword";
        e.Extract("c:\temp\bigdb.mdb");
    }
    

    解压后,您可以创建到访问数据库和数据读取器对象的数据连接。然后使用 dbreader 读取行并写入平面文件(避免大型数据集的内存不足异常)。

    private constr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=yourdbfile.mdb;Jet OLEDB:Database Password=yourpassword;";
    OleDbConnection conn = new OleDbConnection(constr);
    string query = "SELECT * FROM [YourTable]";
    
    OleDbCommand cmd = new OleDbCommand(query, conn);
    OleDbDataReader reader = cmd.ExecuteReader();
    int rowNum = 0;
    StringBuilder sb = new StringBuilder(); 
    while (reader.Read())
    {
       // write rows to flat file in chunks of 10K rows.
       sb.Append(reader["FieldA"].ToString() + "|");
       sb.Append(reader["FieldB"].ToString() + "|");
       sb.Append(reader["FieldC"].ToString() + System.Environment.NewLine);
    
       if (rowNum % 10000 == 0)
       {
            File.AppendText(@"c:\temp\data.psv", sb.ToString());
            sb = new StringBuilder(); 
       }
       rowNum++;
    }
    File.AppendText(@"c:\temp\data.psv", sb.ToString());
    reader.Close();
    

    填写完数据表后,您可以将其导出到平面文件。 我不建议逐行插入数据,这会非常慢,并且会使您的 Oracle 数据库事务日志膨胀。我不相信 Oracle 10g 有支持批量加载的 .Net 驱动程序,所以您可能需要通过平面文件批量加载。

    接下来,通过命令行you can invoke this from your C# console app 导入 Oracle。在执行此操作之前,您需要先创建一个 control file, ctl.ldr,供 Oracle 用于批量加载操作。

    options (skip=1)
    load data
     INFILE 'c:\temp\data.psv'
     INTO table tblTest
     APPEND
     FIELDS TERMINATED BY "|" optionally enclosed by '"'      
     ( fielda,fieldb,etc...)
    
    and then 
    run it in as follows via command line
    
    sqlldr username/pswd@oracle_sid control=ctl.ldr
    

    希望这会有所帮助,祝你好运!

    [编辑]

    您还可以查看 .Net Oracle Bulk copy class。这是随 Oracle 11g client 驱动程序一起提供的。也许它仍然适用于您的 10g 服务器。那里的一个潜在问题是,您在同一应用服务器上的所有其他应用程序也需要与这些较新的 11g 客户端驱动程序一起使用。另一种选择是构建一个使用支持批量加载的Jena framework 的Java 应用程序。

    【讨论】:

    • 我想a.Fill(t1);会抛出一个OutOfMemoryException,我之前用它填充了250k条记录并且失败了,这个访问文件权重1.5GB可能是数百万条记录,反之oracle的看起来很合理,让我看看,谢谢你的努力
    • 好点,你不需要使用数据表,你可以使用数据读取器并以流的方式读取行,只是需要更多的代码。
    • 谢谢!,记住是一个 FTP,但感谢您推荐的 WebClient,我想会使用 ftwebrequest
    • 检查我上次的编辑,我重新设计了平面文件导出以使用具有分块写入模式的数据读取器增加块大小以获得更好的性能,同时考虑到字符串生成器的可用 RAM。
    • 再次感谢,对此感到抱歉,我忘了在问题上提及它,我刚刚编辑它,我的后端服务器和数据库服务器将位于不同的位置,我喜欢你的方法,但生成txt文件我要复制或者直接在oracle中生成,哪个应该更快
    【解决方案3】:

    正如我在评论中询问的那样,可以从 oracle 创建到另一个数据库的 DBLink。因此,我将创建一个应用程序来下载文件并将其解压缩到特定区域(您的 oracle 服务器可以读取的区域)。 然后我会按照这个说明为这个文件设置一个 DBLink https://forums.oracle.com/thread/407779 或 assktom http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:4406709207206

    然后,直接从读取 msAccess db 的 oracle 导入数据。

    【讨论】:

      【解决方案4】:

      查看Pentaho。这基本上是一个类似 Java 开源 SSIS 的产品。下载并提取 Access db 后,此工具应完成其余工作以将数据加载到 Oracle 中。我之前在 MySQL 中使用过它,它运行良好,现在它有一个bulk Oracle db load component。 Pentaho 也不需要在您的数据库服务器上运行,因此这可能是您正在寻找的灵丹妙药。权衡是会有一些加速时间。

      另一个可能的选择是假设您的 Oracle 服务器正在运行 Windows Server,并且您可以将 Access 文件放在该服务器的本地某个位置或服务器可以读取它的位置,您可以执行以下操作。

      1. 创建一个 C# 应用以下载、提取 Access db 并将其复制到 Oracle 可访问的位置。
      2. 在数据库服务器上创建一个本地系统 DSN(ODBC 条目)到 Access db。
      3. 配置并启动 Oracle 侦听器、HS 和连接设置。
      4. 添加数据库链接。
      5. 加入链接的数据库和查询。

      This article概述了配置Oracle db链接访问的整个过程。

      【讨论】:

        【解决方案5】:

        我有两个 Powershell 脚本,第一个帮助我从 中国 下载 ftp 文件:

        下载 FTP 脚本

        # Parametros
        param([string]$url, [string]$user, [string]$pass, [string]$folder, [string]$fecpro)
        
        
        if (!$fecpro)
        {
            $date = Get-Date
            $date = $date.AddDays(-1)
            $fecpro = '{0:yyyyMMdd}' -f $date
        }
        
        $FtpServer     = "ftp://$url/$fecpro"
        $Username      = $user
        $Password      = $pass
        $RemoteFolder  = $FtpServer
        if (!$folder)
        {
            $LocalFolder   = $pwd.path + "\"
        } 
        else 
        {
            $LocalFolder   = $folder + "\"
            if (-not(Test-Path -Path $LocalFolder))
            {
                New-Item -ItemType Directory -Force -Path $LocalFolder
            }
        }
        Write-Host "Searching *.zip files from $RemoteFolder..."
        
        $existfiles = gci $LocalFolder | Where-Object { $_.name -like "*.zip" }
        
        $cntFile = 0
        $totFiles = 1
        $listFiles = @(0)*$totfiles
        $listFiles[1] = "cs2-" + $fecpro + "-6004-0000-1.zip"
        
        $existfiles = gci $LocalFolder | Where-Object { $_.name -like "*.zip" }
        
        do
        {
            try
            {
                $f_exist = $false
                $cntFile = $cntFile + 1
                if ($existfiles) {
                    foreach($file in $existfiles) {
                        $tmpname = $file.name.ToString()
                        $bool = $listFiles[$cntFile].CompareTo($file.name)                              
                        if ($bool -eq 0) {
                            $f_exist = ($f_exist -or $true)
                        } else {
                            $f_exist = ($f_exist -or $false)
                        }
                    }
                }
        
                if (!$f_exist -and $rescnt['CNT'] -eq 0) {
                    $ftpfile = $RemoteFolder + "/" + $listFiles[$cntFile]
                    $FTPRequest1 = [System.Net.FtpWebRequest]::Create($ftpfile)
                    $FTPRequest1.Credentials = New-Object System.Net.NetworkCredential($Username,$Password)
                    $FTPRequest1.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
                    $FTPRequest1.UsePassive = $true
                    $FTPRequest1.UseBinary = $true
                    $FTPRequest1.KeepAlive = $false
                    $destfile = $LocalFolder + $listFiles[$cntFile]
                    $file = New-Object IO.FileStream ($destfile, [IO.FileMode]::Create)
                    $FTPResponse = $FTPRequest1.GetResponse()
                    $ResponseStream = $FTPResponse.GetResponseStream()
                    [byte[]] $buffer = New-Object byte[] 64
                    $FTPReader = $ResponseStream.Read($buffer, 0, 64)
                    while($FTPReader -ne 0){
                        $FTPReader = $ResponseStream.Read($buffer,0, 64)
                        $file.Write($buffer,0,$FTPReader)
                    }
                    Write-Host "File $ftpfile is downloaded in local folder."       
                    $file.Close()               
                    $FTPReader.Close()
                    $FTPResponse.Close()
                    $ResponseStream.Close()
        
                } 
                else {
                        Write-Host "The file $name already exists in local folder."
                }
            }
            catch
            {
                if ($file) {
                    $file.Close()
                }
        
            }
        }
        while ($cntFile -lt $totFiles+1) 
        

        我的第二个脚本使用 Oracle 客户端 附带的 unzip.exe 解压缩文件。 (%ORACLE_HOME%\product\10.2.0\client_1\BIN)

        解压文件脚本

        param([string]$folder_origen, [string]$folder_destino, [string]$fecpro)
        
        $P_FOLDER = $folder_origen + "\"
        $P_DESTINO = $folder_destino + "\"
        
        if (!$fecpro)
        {
            $date = Get-Date
            $date = $date.AddDays(-1)
            $fecpro = '{0:yyyyMMdd}' -f $date
        }
        
        Write-Host "Searching *.zip files in $P_FOLDER"
        
        # Inicio del programa
        $LocalFolder = $P_FOLDER + "*$fecpro*.zip"
        $filelocation = gci $LocalFolder
        foreach ($file in $filelocation){
            if (($file) -and ($file.length -gt 0kb))
            {
                $shell = new-object -com shell.application
                $filename = $file.name.ToString()
                $split = $filename.Split(".")
                $dest = $P_DESTINO + $split[0]
                If (!(Test-Path $dest))
                { 
                    New-Item $dest -Type Directory
                }
                $orifile = $P_FOLDER + $filename
                $time = "{0:hh_mm_ss.ffff}" -f (get-date)
                $prefile = "d." + (get-date).ToShortDateString() + ".t." + $time
                $prefile = $prefile -replace "/", "_"
                $filename = $prefile + ".f." + $filename
                $P_DESTZIP = $P_FOLDER + "proc" + "\"
                If (!(Test-Path $P_DESTZIP))
                { 
                    New-Item $P_DESTZIP -Type Directory
                }
                #Copy-Item $orifile "$P_DESTZIP$prefile_$filename"
                $zipname = $orifile
                #"$P_DESTZIP$prefile_$filename"
                $zip = $shell.NameSpace($zipname)
                $destn = $shell.Namespace($dest)
        
                unzip -o -qq $zipname "*$fecpro*afsbjdtl.*" "*$fecpro*afgnled.bin" -d $dest
        
                Write-Host "$orifile was renamed and unzipped."
            } else
            {
                Write-Host "Zip files not found in the folder."
            }
        }
        

        当我可以使用文件的大小名称等过滤器时,我会使用模式搜索要提取的文件扩展,而且速度非常快。

        最后关于加载,我也建议使用 LOAD DATA 命令。它的性能最好。

        【讨论】:

          【解决方案6】:

          我会说你应该更好地使用你最了解的工具。 可以在 java 中做同样的事情,然后将其加载到数据库中或作为单独的应用程序运行,但为什么要麻烦呢? 我相信您知道在 C# 中下载/解压缩/打开访问数据库的有效方法。

          所以只缺少一件事,即如何有效地将数据从 access 数据库上传到 oracle 数据库。

          简短的回答是-bulk inserts,可能与临时表和存储过程的一些帮助结合使用,以进行进一步的批量处理。通过数据库/表自定义调整以更好地适应您的数据,可以实现更高的加速。

          你也可以在 java 中做同样的事情,但我怀疑它会更快。缓冲数据和批量加载的正确工作比任何其他因素都重要得多。之后,找一些 DBA 和 bug 来帮助你进行数据库调优。细节超出了这个问题的范围。

          【讨论】:

          • 我会考虑做批量插入,我不知道是否可以直接从c#完成,或者我必须制作一个平面文件将它复制到o​​racle并运行sql loader。而且我知道我不会在 java 或 .net 上获得太多时间,但是我正在考虑直接在 oracle 中使用 java 程序,这就是为什么我想到这一点,如果我在 webService Java 或 .NET 上使用它会是一样的东西,但是直接在 oracle 中使用 java,尽管 java 程序或 SQLJ 可能会更快。
          • @Mr 其实不会快多少,只有通过网络访问数据库才会有网络成为瓶颈。 oracle 的 C++ 库可以进行批量插入。数据库内部或外部不会有太大区别。但是使用 BULK INSERT 可以使查询的运行速度提高几倍。所以朝那个方向深入。如果您发现 C# 无法执行 BULK INSERT,则回退到 C++ 或 Java。
          • 它可以做到,我正在制作一个很好的例子来测试它,让您随时发布并感谢您宝贵的时间。似乎批量插入是我缺少的东西
          【解决方案7】:

          对不起,如果我说的很明显,但如果您的主要性能问题是 msaccess.db 的 ftp/sftp 传输速度,那么让 access db 定期导出表/记录的子集可能是有意义的您实际需要访问的权限,以便对 Oracle 进行所需的更新。

          几年前,我发现使用“ArrayDML”向 Oracle 添加记录的速度大约是一次添加一条记录的 10 倍。 (不确定 odp.net 是否支持...)

          【讨论】:

          • 让我看看ArrayDML,Access DB每个月都在变化,所以我每次都要下载
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-11
          • 1970-01-01
          • 2014-11-09
          • 2020-06-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多