C#/SQLite与FoxPro在导入文件方面的性能对比

本文关键字:方面 性能 文件 导入 SQLite FoxPro | 更新日期: 2023-09-27 18:29:29

I"继承"了一些用于操作大型文本文件的遗留软件,该软件目前是用Visual Foxpro(特别是第7版)编写的。

我正在探索用更容易修改和维护的新程序替换这些程序的选项。然而,我无法接近Foxpro在导入大型文本文件方面的性能。

例如,我有一个大约1gb的文本文件,其中有大约110000条记录,每条记录有213个字段(每行9247个字符长),我正在用它进行测试。

使用Foxpro:

? SECONDS()
USE new
APPEND FROM test.dat TYPE sdf
USE
? SECONDS()

在我的计算机上,导入数据库只需不到10秒。

使用C#/SQLite(带有Filehelpers和System.Data.SQLite),我能完成此导入的最快时间是一分钟以上。我已经尝试使用这个问题中的建议(例如事务等)来尽可能地优化它。事实上,如果我没有将导入1gb文件的时间与Foxpro的10秒进行比较,那么导入1gb的时间似乎并不糟糕。

在约1分钟内花费的时间的近似分解为:

Filehelpers reading file and parsing: 12 seconds.
Building SQL commands from Filehelpers object: 15 seconds.
Running individual ExecuteNonQuery()'s: 24 seconds.
Committing transactions (every 5000 records): 12 seconds.

与线程链接相比,我每秒的插入速度要慢得多,但我的记录有213个字段,而不是7个,所以这是意料之中的事。如果我把它按场/秒分解,我大约是360000,而线程是630000。另一张海报的插入率为2.24兆字节/秒,我为15.4兆字节/s。因此,我认为我的性能与其他海报相当,我可能无法进行更多优化。

为什么这比Foxpro的进口慢得多(慢5-6倍)?这只是苹果对桔子吗?我应该接受较慢的速度来换取我使用新技术所获得的其他好处吗?

编辑:

以下是我用随机数据运行的一些测试代码,显示了类似的速度。。。。我是否正确执行了交易?ExecuteNonQuery()花费了大量时间。我已经用简单的、非参数化的、很可能效率低下的字符串操作实现了所有SQL命令,但在这一点上我并不太担心(我将从文件中读取,这种方法比使用实体框架快得多)。

public class Program
{
    public static void Main(string[] args)
    {
        TestSqlite(100, 20000);
    }
    public static void TestSqlite(int numColumns, int numRows)
    {
        Console.WriteLine("Starting Import.... columns: " + numColumns.ToString() + " rows: " + numRows.ToString());
        var conn = new SQLiteConnection(@"Data Source=C:'vsdev'SqliteTest'src'SqliteTest'ddic.db;Version=3");
        conn.Open();
        var cmd = new SQLiteCommand(conn);
        cmd.CommandText = "DROP TABLE IF EXISTS test";
        cmd.ExecuteNonQuery();
        string createCmd = "CREATE TABLE 'test'('testId' INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL, ";
        for (var i = 0; i < numColumns; i++)
        {
            createCmd += "'test" + i.ToString() + "' TEXT, ";
        }
        createCmd = createCmd.Substring(0, createCmd.Length - 2);
        createCmd += ")";
        cmd.CommandText = createCmd;
        cmd.ExecuteNonQuery();
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
        var transaction = conn.BeginTransaction();

        int lineCount = 0;
        long startTime;
        long endTime;
        long totalStringTime = 0;
        long totalAddTime = 0;
        long totalSaveTime = 0;
        string command;
        for (var l = 0; l < numRows; l++)
        {

            startTime = stopWatch.ElapsedMilliseconds;
            command = CreateRandomInsert("test", numColumns);
            endTime = stopWatch.ElapsedMilliseconds;
            totalStringTime += endTime - startTime;

            ///Execute Query
            startTime = stopWatch.ElapsedMilliseconds;
            cmd.CommandText = command;
            cmd.ExecuteNonQuery();
            endTime = stopWatch.ElapsedMilliseconds;
            totalAddTime += endTime - startTime;

            if (lineCount > 5000)
            {
                lineCount = 0;
                startTime = stopWatch.ElapsedMilliseconds;
                transaction.Commit();
                transaction.Dispose();
                transaction = conn.BeginTransaction();
                cmd = new SQLiteCommand(conn);
                endTime = stopWatch.ElapsedMilliseconds;
                totalSaveTime += endTime - startTime;
                Console.Write('.');
            }
            lineCount += 1;
        }
        startTime = stopWatch.ElapsedMilliseconds;
        transaction.Commit();
        transaction.Dispose();
        endTime = stopWatch.ElapsedMilliseconds;
        totalSaveTime += endTime - startTime;

        Console.WriteLine('.');
        Console.WriteLine("String time: " + totalStringTime.ToString());
        Console.WriteLine("ExecuteNonQuery time: " + totalAddTime.ToString() + ", per 1000 records: " + (totalAddTime / (numRows / 1000)).ToString());
        Console.WriteLine("Commit time: " + totalSaveTime.ToString() + ", per 1000 records: " + (totalSaveTime / (numRows / 1000)).ToString());

        stopWatch.Stop();
        TimeSpan ts = stopWatch.Elapsed;
        string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
            ts.Hours, ts.Minutes, ts.Seconds,
            ts.Milliseconds / 10);
        Console.WriteLine(" in " + elapsedTime);
        conn.Close();
    }

    public static string CreateRandomInsert(string TableName, int numColumns)
    {
        List<string> nameList = new List<string>();
        List<string> valueList = new List<string>();
        for (var i = 0; i < numColumns; i++)
        {
            nameList.Add("test" + i.ToString());
            valueList.Add(Guid.NewGuid().ToString());
        }
        return CreateSql(TableName, nameList, valueList);
    }
    public static string CreateSql(string TableName, List<string> names, List<string> values)
    {
        string textCommand = "";
        textCommand += "INSERT INTO " + TableName + " (";
        foreach (var nameVal in names)
        {
            textCommand += nameVal + ", ";
        }
        textCommand = textCommand.Substring(0, textCommand.Length - 2);
        textCommand += ") VALUES (";
        foreach (var val in values)
        {
            textCommand += "'" + val + "', ";
        }
        textCommand = textCommand.Substring(0, textCommand.Length - 2);
        textCommand += ");";
        return textCommand;
    }
}

C#/SQLite与FoxPro在导入文件方面的性能对比

当它是固定宽度的数据时,VFP比其他数据库具有优势。低级别,它几乎与VFP存储数据的格式相同。例如,如果您没有任何与Foxpro 2.x不兼容的字段,那么导入数据只意味着在每行前面添加一个0x20字节(如果还没有写入文件头,则写入文件头),如果有索引,则更新索引,这将是真正耗时的部分。如果您需要解析这些行,那么与C#相比,VFP在字符串操作方面相当慢。

(我不知道FileHelpers,谢谢你指出它)

9247字节的固定数据和213个字段是令人感兴趣的。我想测试插入SQLite、postgreSQL、SQL server和一些NoSQL数据库。您是否可以共享一个空的dbf(或者只是作为代码或xml的结构——可能与文件中的1-2行示例数据共享)?我以前没有尝试过200多个字段,否则1分钟110K行听起来很慢。

VFP表只能追加2次这样的数据,并且您需要创建另一个表。你说的是对文本文件的"操纵",但你没有评论这是什么类型的操纵。也许不附加到表中的直接"操作"更高效、更快,整个过程完成得更快?只是一个想法。