【问题标题】:1GB of Data From MySQL to MS Access从 MySQL 到 MS Access 的 1GB 数据
【发布时间】:2015-10-14 14:47:17
【问题描述】:

情况:我正在创建一个自动任务,它查询 MySQL(通过 ODBC)并使用 OLEDB 将结果集插入 MS Access 数据库 (.mdb)。

守则:

OleDbConnection accCon = new OleDbConnection();
OdbcCommand mySQLCon = new OdbcCommand();
try
{
    //connect to mysql
    Connect();                
    mySQLCon.Connection = connection;              

    //connect to access
    accCon.ConnectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;" +
        @"Data source= " + pathToAccess;
    accCon.Open();
    var cnt = 0;

    while (cnt < 5)
    {
        if (accCon.State == ConnectionState.Open)
            break;
        cnt++;
        System.Threading.Thread.Sleep(50);
    }

    if (cnt == 5)
    {
        ToolBox.logThis("Connection to Access DB did not open. Exit Process");
        return;
    }
} catch (Exception e)
{
    ToolBox.logThis("Faild to Open connections. msg -> " + e.Message + "\\n" + e.StackTrace);
}
OleDbCommand accCmn = new OleDbCommand();
accCmn.Connection = accCon;
//access insert query structure
var insertAccessQuery = "INSERT INTO {0} values({1});";
// key = > tbl name in access, value = > mysql query to b executed
foreach (var table in tblNQuery)
{
    try
    {
        mySQLCon.CommandText = table.Value;
        //executed mysql query                        
        using (var dataReader = mySQLCon.ExecuteReader())
        {
            //variable to hold row data
            var rowData = new object[dataReader.FieldCount];
            var parameters = "";
            //read the result set from mysql query
            while (dataReader.Read())
            {
                //fill rowData with the row values
                dataReader.GetValues(rowData);
                //build the parameters for insert query
                for (var i = 0; i < dataReader.FieldCount; i++)
                    parameters += "'" + rowData[i] + "',";

                parameters = parameters.TrimEnd(',');
                //insert to access
                accCmn.CommandText = string.Format(insertAccessQuery, table.Key, parameters);
                try
                {
                    accCmn.ExecuteNonQuery();
                }
                catch (Exception exc)
                {
                    ToolBox.logThis("Faild to insert to access db. msg -> " + exc.Message + "\\n\\tInsert query -> " + accCmn.CommandText );
                }                              
                parameters = "";
            }
        }
    }
    catch (Exception e)
    {
        ToolBox.logThis("Faild to populate access db. msg -> " + e.Message + "\\n" + e.StackTrace);
    }
}
Disconnect();
accCmn.Dispose();
accCon.Close();

问题:

  1. 内存使用率非常高 (300MB++),而 MS Access 文件大小不会不断变化!似乎插入缓存数据而不是将其保存到磁盘。

  2. 非常慢!我知道我的查询会在几秒钟内执行,但插入过程需要很长时间。

我尝试在 MS Access 中使用准备好的语句并将值作为参数插入而不是字符串 concat 来创建插入查询。但是我收到此异常消息:

条件表达式中的数据类型不匹配。

有人知道如何解决这个问题或有更好的方法吗?

【问题讨论】:

  • 请在投反对票后发表评论>:(
  • MS Access 不符合 ACID,并且 MS 通过在刷新之前将内存用作缓存来利用这一点。由于每行插入的行数以及 MySQL 数据库的位置,所花费的时间可能会很长:它是在同一台机器上还是两者之间有网络?
  • 都在同一台机器上。我可以强制它刷新数据到磁盘吗?!
  • 你可以试试这个:msdn.microsoft.com/en-us/library/… 但是很有可能它不起作用,因为你使用了超过 1 个 oledb 连接,在旧版本中导致 MS Access(按设计),而不是出于性能原因刷新。

标签: c# mysql ms-access odbc oledb


【解决方案1】:

您可以创建一个 VBA 宏,该宏使用 DoCmd.TransferDatabase 方法通过 ODBC 将数据提取到您的 Access 数据库中。它可能会更快更简单。

要从外部程序或计划任务运行 VBA 代码,只需启动 Access 以使用 /x 命令行开关打开文件,它将在启动时运行导入宏。不过,1 GB 的数据仍然需要一段时间。我找到了article by David Catriel that implemented this approach

更好的选择是使用不同的数据库引擎后端,例如免费版本的 SQL Server Express。然后你有更多的选择,它更强大。如果您需要 MS Access 表单和报告,如果您使用 SQL Server,则可以创建一个 ADP 项目文件,或者您可以使用链接表来获取您的数据。如果满足您的要求,您甚至可以use Access as a front-end to your MySQL database 而不是复制所有数据。

【讨论】:

  • 感谢 Brian 的回答,但我无法在服务器机器上安装 MS Access :(
  • 它不必在服务器机器上。在任何安装了 MS Access 的计算机上执行此操作,然后将 mdb 复制到服务器(如有必要)。它仍然会快得多。
  • 是的,您仍然需要偶尔手动运行压缩并修复您的数据库。您可能会考虑使用不同的数据库后端,例如 SQL Sever Express(添加到我的答案中)。
  • MS Access 数据库将传递给客户。我们不能给他们所有的数据库访问权限或将单独的机器链接到服务器。也无法迁移到 SQL Server。如果没有更简单的方法,我想我会忍受内存使用。
  • 如果用户没有连接到服务器上的访问数据库(您将其提供给客户),那么 DoCmd.TransferDatabase 方法应该可以工作。我用大约相同数量的数据完成了它。你只需要在安装了 MS Access 和 MySql 数据库的 ODBC 链接的机器上运行你的宏。然后,您可以根据需要删除宏并将其传递给您的客户。
【解决方案2】:

您可以求助于 SQL Server Integration Services (SSIS),而不是编写代码,并在午餐前完成。它以extension to Visual Studio 的形式提供,以防您的计算机上还没有安装 SQL Server。

使用 SSIS,您可以创建可从命令行或计划任务触发的可重用 SSIS 包。 This guide 展示了如何将数据从 MySQL 拉到 SQL Server 中,但是 SQL Server 部分应该很容易replace with Access

【讨论】:

    【解决方案3】:

    对注释进行了一些更改,以添加用于命令执行的事务。如果不手动控制事务,每次都会自动创建和提交,这是一个耗时的操作

                OleDbConnection accCon = new OleDbConnection();
                OdbcCommand mySQLCon = new OdbcCommand();
                try
                {
                    //connect to mysql
                    Connect();
                    mySQLCon.Connection = connection;
    
                    //connect to access
                    accCon.ConnectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;" +
                                              @"Data source= " + pathToAccess;
                    accCon.Open();
                    var cnt = 0;
    
                    while (cnt < 5)
                    {
                        if (accCon.State == ConnectionState.Open)
                            break;
                        cnt++;
                        System.Threading.Thread.Sleep(50);
                    }
    
                    if (cnt == 5)
                    {
                        ToolBox.logThis("Connection to Access DB did not open. Exit Process");
                        return;
                    }
                }
                catch (Exception e)
                {
                    ToolBox.logThis("Faild to Open connections. msg -> " + e.Message + "\\n" + e.StackTrace);
                }
    //AMK: transaction starts here
                var transaction = accCon.BeginTransaction();
                OleDbCommand accCmn = new OleDbCommand();
    
                accCmn.Connection = accCon;
                accCmn.Transaction = transaction;
    //access insert query structure
                var insertAccessQuery = "INSERT INTO {0} values({1});";
    // key = > tbl name in access, value = > mysql query to b executed
                foreach (var table in tblNQuery)
                {
                    try
                    {
                        mySQLCon.CommandText = table.Value;
                        //executed mysql query                        
                        using (var dataReader = mySQLCon.ExecuteReader())
                        {
                            //variable to hold row data
                            var rowData = new object[dataReader.FieldCount];
                            var parameters = "";
                            //read the result set from mysql query
                            while (dataReader.Read())
                            {
                                //fill rowData with the row values
                                dataReader.GetValues(rowData);
                                //build the parameters for insert query
                                for (var i = 0; i < dataReader.FieldCount; i++)
                                    parameters += "'" + rowData[i] + "',";
    
                                parameters = parameters.TrimEnd(',');
                                //insert to access
                                accCmn.CommandText = string.Format(insertAccessQuery, table.Key, parameters);
                                try
                                {
                                    accCmn.ExecuteNonQuery();
                                }
                                catch (Exception exc)
                                {
                                    ToolBox.logThis("Faild to insert to access db. msg -> " + exc.Message +
                                                    "\\n\\tInsert query -> " + accCmn.CommandText);
                                }
                                parameters = "";
                            }
                        }
    //AMK: transaction commits here if every thing is going well
                        transaction.Commit();
                    }
                    catch (Exception e)
                    {
                        ToolBox.logThis("Faild to populate access db. msg -> " + e.Message + "\\n" + e.StackTrace);
    //AMK: transaction rollback here if there is a problem
                        transaction.Rollback();
                    }
                }
                Disconnect();
                accCmn.Dispose();
                accCon.Close();
    

    【讨论】:

    • 我尝试了您的代码并稍作修改。但它不起作用。我希望每次提交时内存使用量都会下降,.mdb 的大小会增加。但它没有发生,交易并没有解决问题
    • 加速/增加文件大小?哪一个更适合你?如果您希望文件大小增加,您可以通过关闭连接并重新打开它来推送数据或强制刷新数据可能会有所帮助。但在速度方面,这是最有效的解决方案。
    • 我需要一个共同点。我不想让它比现在慢,但内存使用更重要。它有时会达到 600MB,这是不能容忍的。我将继续关闭并重新打开连接作为我的最后一个选择。
    【解决方案4】:

    为 SQL Server 数据库创建一个 DSN(数据源名称)。然后通过打开 Microsoft Access 数据库并选择从该 DSN 导入来选择该 DSN。您应该能够导入准确的 1GB 表(架构、数据、所有内容)。

    有关使用 DSN 的更多信息: https://support.office.com/en-us/article/Link-to-SQL-Server-data-0474c16d-a473-4458-9cf7-f369b78d3db8

    或者,您可以使用该 DSN 链接到 SQL 服务器数据库(不导入 Access 表)并完全跳过导入。

    【讨论】:

      【解决方案5】:

      INSERT 是否应该是 TRANSACTION 的一部分。在 TRANSACTION 中通常会加快 BULK INSERTS

      【讨论】:

        【解决方案6】:

        感谢大家的回答。我刚刚在我的代码中发现了主要问题。大量内存使用(问题 #1)的原因是 ODBC 正在缓存来自 MySQL 的数据,而不考虑 C# 方法(DataReader)。该问题通过选中 DSN 设置中的 Don't cache results of forward-only cursors 复选框得到解决。这也使这个过程稍微快了一点(30%)。然而,更具体的方法仍然是 Brian Pressler 和 Egil Hansen 建议的。但由于它们需要软件安装和/或迁移计划,最简单的方法是坚持这段代码。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-09-15
          • 2016-09-14
          相关资源
          最近更新 更多