C# 如何防止在长时间运行的查询期间因崩溃而丢失数据?

Posted

技术标签:

【中文标题】C# 如何防止在长时间运行的查询期间因崩溃而丢失数据?【英文标题】:C# How do I prevent data loss from crashes during a long running query? 【发布时间】:2017-08-02 07:44:12 【问题描述】:

我有以下代码需要大约一个小时才能运行几十万行:

public void Recording(int rowindex)
    
        using (OleDbCommand cmd = new OleDbCommand())
        
            try
            
                using (OleDbConnection connection = new OleDbConnection(Con))
                
                    cmd.Connection = connection;
                    connection.Open();
                    using (OleDbTransaction Scope = connection.BeginTransaction(SD.IsolationLevel.ReadCommitted))
                    
                        try
                        
                            string Query = @"UPDATE [" + SetupAction.currentTable + "] set Description=@Description, Description_Department=@Description_Department, Accounts=@Accounts where ID=@ID";
                            cmd.Parameters.AddWithValue("@Description", VirtualTable.Rows[rowindex][4].ToString());
                            cmd.Parameters.AddWithValue("@Description_Department", VirtualTable.Rows[rowindex][18].ToString());
                            cmd.Parameters.AddWithValue("@Accounts", VirtualTable.Rows[rowindex][22].ToString());
                            cmd.Parameters.AddWithValue("@ID", VirtualTable.Rows[rowindex][0].ToString());
                            cmd.CommandText = Query;
                            cmd.Transaction = Scope;
                            cmd.ExecuteNonQuery();
                            Scope.Commit();
                        
                        catch (OleDbException odex)
                        
                            MessageBox.Show(odex.Message);
                            Scope.Rollback();
                        
                    
                
            
            catch (OleDbException ex)
            
                MessageBox.Show("SQL: " + ex);
            
        
    

它按我的预期工作,但是今天我的程序在运行查询时崩溃了(在 for 循环中,rowindex 是数据表的索引),计算机崩溃了,当我重新启动程序时,它说:

多步 OleDB 操作产生错误:后跟我的连接字符串。

发生的事情是数据库完全无法交互,甚至 microsoft access 的恢复方法似乎也无济于事。

我了解到,这可能是由于数据库的数据结构与预期的不同而导致的。我的问题是,我该如何防止这种情况发生,因为我无法真正检测到我的程序是否突然停止运行。

我可能有办法以某种方式对其进行重组,也许有一个我不知道的功能。崩溃发生时,它可能正在发送一些空查询,但我不知道如何停止它。

【问题讨论】:

这段代码看起来效率极低。它不仅为每个查询执行重复打开和关闭数据库连接,而且还使用其所有参数重新创建命令。如果您打开并维护连接并创建一次命令对象,则执行时间将大大减少。然后只需重新分配参数值并重新执行。似乎也没有必要为更新一行数据的单个查询启动和提交事务。这样一个简单的操作将自行失败或成功,无需事务。 另外,请详细说明“访问的恢复方法”。列出您尝试过的各种步骤,或者分享包含这些步骤的资源链接。 不要只是“取消...提交”。如果一个事务完全启动(即`connection.BeginTransaction'),那么您必须提交或回滚,否则数据库将处于不一致状态并且不会保存更新。您的教授正在教授良好的习惯,并且对于任何一组多个操作或影响许多记录的查询,事务确保它们全部一起提交或不提交。但是对于更新单行单查询,可能不需要单独的事务,因为失败不需要回滚其他记录。 相反,问问自己整个应该循环是否在一个事务中——单个更新是依赖的还是独立的。换句话说,如果单个更新失败,您是否希望回滚成千上万的其他更新?如果您确实想要这种行为,那么您在创建命令对象的同时在循环外启动事务,然后在循环结束时提交所有更改。如果即使一次更新失败,那么您会立即退出循环并回滚所有更改。这当然需要在解决任何问题后运行整个循环。 对于有关交易的过多 cmets 感到抱歉,但我必须稍微限定一下我的陈述。首先,关于不需要单行更新的事务,这假设没有可能对数据库进行额外更改的数据宏(或触发器,因为它们将在其他数据库系统上调用)。使用适当的术语,更新单行已经是一个原子操作。您不能将这样的更新分成多个部分……它要么更新单行,要么不更新。 【参考方案1】:

Jet/ACE 数据库引擎已经尝试避免损坏并从灾难性事件(丢失连接、计算机崩溃)中自动恢复。通过完全提交(或丢弃)多个操作,事务可以进一步防止数据不一致。但最终可能会出现一些偶然的系统故障,这可能会在某个关键写入位置终止操作,从而在数据库文件中产生严重的不一致。进行定期和及时的备份是整体解决方案的一部分。对于非常长的操作,可能值得在操作之前自动复制整个数据库文件。

否则,一个极端的选择是

    创建第二个中间数据库,首先将所有数据插入其中。 (只需执行一次。) 在此中间数据​​库中,创建与永久工作数据库中相关表的链接表。 同样在中间数据库中,创建一个索引本地表,该表反映将插入数据的链接表结构。或者如果中间数据库和表已经存在,清除本地表(即删除所有行)。 将您当前的软件插入到本地中间表中。 运行一个查询,然后从临时表中更新链接表。将该更新包装在事务中。 链接表的优势在于它可以像任何本地表一样在 SQL 查询中被引用。您只需显式打开中间数据。换句话说,只需执行一个简单的查询,如UPDATE LocalTable INNER JOIN LinkedTable ON LocalTable.UpdateID = LinkedTable.ID SET LinkedTable.Data = LocalTable.Data

此过程的好处是,从另一个 Access 表更新一个 Access 表的单个查询可以非常快,可能比代码中的多个更新操作快得多。这可以降低更新代码中的错误对数据库产生负面影响的可能性。这当然不能完全消除可能影响数据库的随机计算机崩溃,但减少执行多个连接和更新查询的时间可能会降低这种可能性。

【讨论】:

【参考方案2】:

我认为你的catch块是错误的,因为如果你得到OleDbException以外的异常,你不会回滚事务

try

    // ...
    Scope.Commit();

catch (Exception ex)

    MessageBox.Show(ex.Message);
    Scope.Rollback();

Exception 而不是 OleDbException。异常可能来自任何地方,不一定来自 Ole DB,在这种情况下,您仍然希望回滚迄今为止所做的一切。

话虽如此,如果您有几十万行,我会认真考虑批量更新,每次迭代只处理几千行每次迭代的事务

就事务行为而言,主要问题是:您真的想回滚所有您迄今为止更新的内容以防万一失败,或者只是重试/继续您的离开?如果答案是您想重试/继续,那么您可能希望创建一个 BatchUpdateTask 表或类似的表...包含每次迭代所需的所有信息

【讨论】:

嗯,我一直想知道何时该偏离 Exception ex。实际上,我在其他所有 try catch 上都有这个,但是 SO 上有人说我的 catch 异常必须是具体的,在这种情况下,它是 OLeDB。不确定 access 是否有这样的功能,但我会尝试寻找。 解决问题的另一种方法是依靠 Access stored procedures 但显然你需要 2013 年 @AndrésRobinet 根据在线文档,从 2010 版开始支持 CREATE PROCEDURE 命令。(在 Access 中,这些显示为带参数的查询。)但这将如何帮助排除故障? @CPerkins 我想象了一种情况,即在一些操作后从另一个表更新。也许这里不是这种情况,但通常情况下,在内存中实现源数据以更新数千条记录是一种性能消耗。在 SQL 引擎中做尽可能多的工作往往更快、更安全。这与事务本身无关,但我可以想象一个程序由于长时间运行的事务而崩溃。您可以为此目的使用过程和触发器***.com/questions/3287545/… @AndrésRobinet 同意。这实际上是我发布的关于中间数据库表和更新查询的答案的基础,但是我(天真地)假设源数据最初不在 Access 数据库中(因此数据库引擎无法直接访问数据)。如果 OP 代码中的 VirtualTable 对象已经在 Access 数据库中有源,那么肯定有更有效的方法来更新表,而不是从内存中逐行插入。在那种情况下,整个方法可能会被重新设计,然后存储过程可能会很有用。

以上是关于C# 如何防止在长时间运行的查询期间因崩溃而丢失数据?的主要内容,如果未能解决你的问题,请参考以下文章

防止 VS C# 单元测试因异常而中断

使用查询调控器防止查询长时间运行

为啥我的长时间运行的 python 脚本在运行大约 3 天后会因“无效指针”而崩溃?

SQL Server 长时间运行的查询需要数小时但使用的 CPU 较低

如何防止 Oracle SQL Developer 关闭数据库连接?

使用 ReSharper,如何在长时间运行的单元测试期间显示调试输出?