使用C#将大数据导入性能提升到SQLite

时间:2011-11-11 09:29:59

标签: c# .net sqlite system.data.sqlite

我正在使用C#导入一个包含6-8百万行的CSV。

我的表格如下:

CREATE TABLE [Data] ([ID] VARCHAR(100)  NULL,[Raw] VARCHAR(200)  NULL)
CREATE INDEX IDLookup ON Data(ID ASC)

我正在使用System.Data.SQLite进行导入。

目前在Windows 7 32bit,Core2Duo 2.8Ghz& 4GB RAM。这不是太糟糕,但我只是想知道是否有人能够更快地看到导入它的方式。

这是我的代码:

public class Data
{
  public string IDData { get; set; }
  public string RawData { get; set; }
}   

string connectionString = @"Data Source=" + Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + "\\dbimport");
System.Data.SQLite.SQLiteConnection conn = new System.Data.SQLite.SQLiteConnection(connectionString);
conn.Open();

//Dropping and recreating the table seems to be the quickest way to get old data removed
System.Data.SQLite.SQLiteCommand command = new System.Data.SQLite.SQLiteCommand(conn);
command.CommandText = "DROP TABLE Data";
command.ExecuteNonQuery();
command.CommandText = @"CREATE TABLE [Data] ([ID] VARCHAR(100)  NULL,[Raw] VARCHAR(200)  NULL)";
command.ExecuteNonQuery();
command.CommandText = "CREATE INDEX IDLookup ON Data(ID ASC)";
command.ExecuteNonQuery();

string insertText = "INSERT INTO Data (ID,RAW) VALUES(@P0,@P1)";

SQLiteTransaction trans = conn.BeginTransaction();
command.Transaction = trans;

command.CommandText = insertText;
Stopwatch sw = new Stopwatch();
sw.Start();
using (CsvReader csv = new CsvReader(new StreamReader(@"C:\Data.txt"), false))
{
   var f = csv.Select(x => new Data() { IDData = x[27], RawData = String.Join(",", x.Take(24)) });

   foreach (var item in f)
   {
      command.Parameters.AddWithValue("@P0", item.IDData);
      command.Parameters.AddWithValue("@P1", item.RawData);
      command.ExecuteNonQuery();
   }
 }
 trans.Commit();
 sw.Stop();
 Debug.WriteLine(sw.Elapsed.Minutes + "Min(s) " + sw.Elapsed.Seconds + "Sec(s)");
 conn.Close();

4 个答案:

答案 0 :(得分:11)

对于600万条记录而言,这是非常快的。

看来你正在以正确的方式做到这一点,前段时间我在sqlite.org上看到,在插入记录时你需要将这些插入放在事务中,如果你不这样做,你的插入将受到限制每秒只有60!这是因为每个插入都将被视为一个单独的事务,每个事务必须等待磁盘完全旋转。你可以在这里阅读完整的解释:

http://www.sqlite.org/faq.html#q19

  

实际上,SQLite很容易在普通台式计算机上每秒执行50,000或更多INSERT语句。但它每秒只会进行几十次交易。交易速度受磁盘驱动器转速的限制。事务通常需要两个完整的磁盘盘旋转,这在7200RPM磁盘驱动器上限制你每秒约60个事务。

比较您的时间与上述平均值:每秒50,000; =>应该花费2分00秒。这比你的时间快一点。

  

事务速度受磁盘驱动器速度的限制,因为(默认情况下)SQLite实际上等待,直到事务完成之前数据确实安全地存储在磁盘表面上。这样,如果您突然断电或者操作系统崩溃,您的数据仍然是安全的。有关详细信息,请阅读SQLite中的原子提交..

     

默认情况下,每个INSERT语句都是自己的事务。但是如果用BEGIN ... COMMIT包围多个INSERT语句,则所有插入都被分组到一个事务中。提交事务所需的时间在所有随附的insert语句中分摊,因此每个insert语句的时间大大减少。

下一段中有一些暗示可以尝试加速插入:

  

另一种选择是运行PRAGMA synchronous = OFF。此命令将导致SQLite不等待数据到达磁盘表面,这将使写入操作看起来更快。但是如果你在事务中断电,你的数据库文件可能会损坏。

我一直以为SQLite是为“简单的东西”而设计的,在我看来,600万条记录对于像MySQL这样的真实数据库服务器来说是一项工作。

使用如此多的记录计算SQLite表中的记录可能需要很长时间,仅为了您的信息,而不是使用SELECT COUNT(*),您总是可以使用非常快的SELECT MAX(rowid),但不是如果您要删除该表中的记录,那么准确无误。

EDIT。

正如Mike Woodhouse所说,在插入记录后创建索引应该加速整个事情,这是其他数据库中的常见建议,但不能肯定地说它在SQLite中是如何工作的。

答案 1 :(得分:5)

您可能尝试的一件事是在插入数据后创建索引 - 通常,数据库在单个操作中构建索引要比在每次插入(或事务)后更新它快得多)。

我不能说它肯定会与SQLite一起使用,但因为它只需要两行来移动它值得尝试。

我也想知道600万行事务是否会走得太远 - 您是否可以更改代码以尝试不同的事务大小?说100,1000,10000,100000?有没有“甜蜜点”?

答案 2 :(得分:1)

以下列方式绑定参数可以获得相当长的时间:

...
string insertText = "INSERT INTO Data (ID,RAW) VALUES( ? , ? )";  // (1)

SQLiteTransaction trans = conn.BeginTransaction();
command.Transaction = trans;

command.CommandText = insertText;

//(2)------
   SQLiteParameter p0 = new SQLiteParameter();
   SQLiteParameter p1 = new SQLiteParameter();
   command.Parameters.Add(p0);
   command.Parameters.Add(p1);
//---------

Stopwatch sw = new Stopwatch();
sw.Start();
using (CsvReader csv = new CsvReader(new StreamReader(@"C:\Data.txt"), false))
{
   var f = csv.Select(x => new Data() { IDData = x[27], RawData = String.Join(",", x.Take(24)) });

   foreach (var item in f)
   {
      //(3)--------
         p0.Value = item.IDData;
         p1.Value = item.RawData;
      //-----------
      command.ExecuteNonQuery();
   }
 }
 trans.Commit();
...

在第1,2和3部分进行更改。 通过这种方式,参数绑定似乎要快得多。 特别是当你有很多参数时,这种方法可以节省很多时间。

答案 3 :(得分:0)

我做了类似的导入,但我让我的c#代码首先将数据写入csv,然后运行sqlite import实用程序。我可以用这种方式在大约10分钟内输入超过3亿条记录。

不确定是否可以直接从c#进行此操作。