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;
}
}
当它是固定宽度的数据时,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次这样的数据,并且您需要创建另一个表。你说的是对文本文件的"操纵",但你没有评论这是什么类型的操纵。也许不附加到表中的直接"操作"更高效、更快,整个过程完成得更快?只是一个想法。