为啥这段代码变慢了?

Posted

技术标签:

【中文标题】为啥这段代码变慢了?【英文标题】:Why is this code slowing down?为什么这段代码变慢了? 【发布时间】:2013-04-05 08:14:24 【问题描述】:

我目前正在将多个 Access 数据库转换为 Xml 文件。我以前做过这个,我仍然有以前项目的代码。但是,这段代码不会让我随意构建 xml,这是我这次需要做的。我正在使用 XDocumentfor-loops 来实现这一点,但是在 1000 行数据之后它变得非常慢。

阅读 XDocument 的工作原理告诉我,XElement.Add 实际上复制了整个 xml 代码并在将所有内容粘贴回文件时添加了新元素。如果这是真的,那么这可能就是问题所在。

这是从Access中读写数据到Xml的部分,看看有没有办法保存。转换具有 27 列和 12256 行的数据库需要将近 30 分钟,而只有 500 行的较小数据库大约需要 5 秒。

private void ReadWrite(string file)

    using (_Connection = new OleDbConnection(string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Mode=12;Data Source=0", pathAccess)))
    
        _Connection.Open();
        //Gives me values from the AccessDB: tableName, columnName, colCount, rowCount and listOfTimeStamps.
        GetValues(pathAccess);

        XDocument doc = new XDocument(new XDeclaration("1.0", "utf-8", "true"), new XElement(tableName));
        for (int rowInt = 0; rowInt < rowCount; rowInt++)
        
            XElement item = new XElement("Item", new XAttribute("Time", listOfTimestamps[rowInt].ToString().Replace(" ", "_")));
            doc.Root.Add(item);

            //colCount"-1" prevents the timestamp from beeing written again.
            for (int colInt = 0; colInt < colCount - 1; colInt++)
            
                using (OleDbCommand cmnd = new OleDbCommand(string.Format("SELECT 0 FROM 1 Where TimeStamp = #2#", columnName[colInt] , tableName, listOfTimestamps[rowInt]), _Connection))
                
                    XElement value = new XElement(columnName[colInt], cmnd.ExecuteScalar().ToString());
                    item.Add(value);
                
            
            //Updates progressbar
            backgroundWorker1.ReportProgress(rowInt);
        
        backgroundWorker1.ReportProgress(0);
        doc.Save(file);
    

这是我的旧转换器的代码。这段代码几乎不受数据库大小的影响,12 556 数据库只需要一秒钟即可转换。有没有办法合并这两者?

public void ReadWrite2(string file)

    DataSet dataSet = new DataSet();
    using (_Connection = new OleDbConnection(string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Mode=12;Data Source=0", file)))
    
        _Connection.Open();

        DataTable schemaTable = _Connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[]  null, null, null, "TABLE" );

        foreach (DataRow dataTableRow in schemaTable.Rows)
        
            string tableName = dataTableRow["Table_Name"].ToString();

            DataTable dataTable = dataSet.Tables.Add(tableName);
            using (OleDbCommand readRows = new OleDbCommand("SELECT * from " + tableName, _Connection))
            
                OleDbDataAdapter adapter = new OleDbDataAdapter(readRows);
                adapter.Fill(dataTable);
            
        
    
    dataSet.WriteXml(file.Replace(".mdb", ".xml"));

编辑: 澄清一下,应用程序在执行时会变慢。无论数据库有多大,前 500 次都需要 5 秒。

更新:好的,所以我周末后回来了,我在代码中做了一个小的调整,通过在一个循环中用值填充一个锯齿状数组来分离读取和写入并将它们写在另一个中。这证明了我的理论是错误的,事实上阅读需要花费很多时间。关于如何在不访问循环内的数据库的情况下用值填充数组的任何想法?

UPDATE2:这是切换到DataReader.Read()-loop 并立即收集所有数据后的最终结果。

public void ReadWrite3(string Save, string Load)
    
        using (_Connection = new OleDbConnection(string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Mode=12;Data Source=0", Load)))
        
            _Connection.Open();
            GetValues(_Connection);

            _Command = new OleDbCommand(String.Format("SELECT 0 FROM 1", strColumns, tables), _Connection);
            XDocument doc = new XDocument(new XDeclaration("1.0", "utf-8", "true"), new XElement("plmslog", new XAttribute("machineid", root)));
            using (_DataReader = _Command.ExecuteReader())
            
                for (int rowInt = 0; _DataReader.Read(); rowInt++ )
                
                    for (int logInt = 0; logInt < colCount; logInt++)
                    

                        XElement log = new XElement("log");
                        doc.Root.Add(log);

                        elementValues = updateElementValues(rowInt, logInt);

                        for (int valInt = 0; valInt < elements.Length; valInt++)
                        
                            XElement value = new XElement(elements[valInt], elementValues[valInt]);
                            log.Add(value);
                        
                    
                
            
            doc.Save(Save);
        
    

【问题讨论】:

您是否尝试过分析您的代码以查看瓶颈在哪里? @ChrisBint 不,我没有。我可以在没有任何第三方工具或软件的情况下这样做吗? 【参考方案1】:

请原谅我,但我认为你让你的生活变得比它需要的更复杂。如果您使用 OleDbDataReader 对象,您可以打开它并逐行读取 Access 表,而无需将行数据缓存在数组中(因为您已经在 DataReader 中拥有它)。

例如,我有一些样本数据

dbID    dbName  dbCreated
----    ------  ---------
bar     barDB   2013-04-08 14:19:27
foo     fooDB   2013-04-05 11:23:02

下面的代码贯穿整个表格...

static void Main(string[] args)

    OleDbConnection conn = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Documents and Settings\Administrator\Desktop\Database1.accdb;");
    conn.Open();

    OleDbCommand cmd = new OleDbCommand("SELECT * FROM myTable", conn);
    OleDbDataReader rdr = cmd.ExecuteReader();

    int rowNumber = 0;
    while (rdr.Read())
    
        rowNumber++;
        Console.WriteLine("Row " + rowNumber.ToString() + ":");
        for (int colIdx = 0; colIdx < rdr.FieldCount; colIdx++)
        
            string colName = rdr.GetName(colIdx);
            Console.WriteLine("    rdr[\"" + colName + "\"]: " + rdr[colName].ToString());
        
    
    rdr.Close();
    conn.Close();

    Console.WriteLine("Done.");

...并产生结果...

Row 1:
    rdr["dbID"]: foo
    rdr["dbName"]: fooDB
    rdr["dbCreated"]: 2013-04-05 11:23:02
Row 2:
    rdr["dbID"]: bar
    rdr["dbName"]: barDB
    rdr["dbCreated"]: 2013-04-08 14:19:27
Done.

【讨论】:

谢谢!这要快得多,我可以在不到 5 秒的时间内转换大数据库!唯一的问题似乎是,如果我尝试更新进度条,表单会在转换时冻结。不过,这不是问题,因为执行速度不够慢,以至于需要进度条。再次感谢!我将更新问题以显示最终结果。【参考方案2】:

您正在从嵌套循环(所有行和列)内部访问数据库

 using (OleDbCommand cmnd = new OleDbCommand(string.Format("SELECT 0 FROM 1 

最好将数据保存在数组或集合中,然后访问数据库。

【讨论】:

我对此有所了解,但我不确定这是否会解决减速部分,因为我认为这与写作而不是阅读有关。不过尝试一下也无妨,感谢您的意见! 更新: 好的,所以我周末后回来了,我对代码进行了一些小的调整,通过填充一个锯齿状数组来分离读取和写入一个循环中的值并将它们写入另一个循环。这证明了我的理论是错误的,事实上阅读需要花费很多时间。你是对的。您对如何在不访问循环内的数据库的情况下填充数组有任何想法吗?我认为您在这里有所作为。 如果这个问题已经解决了,你能标记正确的答案吗,谢谢。【参考方案3】:

简单的数学计算会告诉你原因。 (数据量)

27 * 12256 = 330912

27 * 500 = 13500

330912 / 13500 = 24512

所以你的大声明要大 24512 倍!

(时间方面)

30*60 = 1800

1800 / 5 = 360

所以你的时间是 360 倍!

您可以看到您的代码似乎没有做坏事。

【讨论】:

这不会导致代码以恒定的速度变慢吗?我之前正在尝试使用OleDbDataReaderwhile(dataReader.read)-loop,它会根据数据库中的数据量保持恒定的速度。

以上是关于为啥这段代码变慢了?的主要内容,如果未能解决你的问题,请参考以下文章

当我使用 std::algorithms 而不是普通循环时,为啥这段代码会变慢?

Arthas - 线上某个接口突然变慢了怎么查

为啥我的触摸事件频率在 iOS 4.0 上变慢了?

为啥IE突然变慢?

为啥我装双网卡共享上网后 打开网页速度变慢了????

sql 数据量很大 ,为啥加了order by速度变慢了?