【问题标题】:Improve large data import performance into SQLite with C#使用 C# 提高大数据导入 SQLite 的性能
【发布时间】:2011-12-26 21:04:56
【问题描述】:

我正在使用 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 32 位、Core2Duo 2.8Ghz 和 4GB RAM 上完成 600 万行需要 2 分 55 秒。这还不错,但我只是想知道是否有人能找到更快导入它的方法。

这是我的代码:

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();

【问题讨论】:

  • 你能从你的数据库之外的另一个卷读取你的输入文件吗?

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


【解决方案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 部分进行更改。 这样,参数绑定似乎要快很多。 尤其是当你有很多参数时,这种方法可以节省不少时间。

【讨论】:

    【解决方案2】:

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

    不确定这是否可以直接从 c# 完成。

    【讨论】:

      【解决方案3】:

      这对于 600 万条记录来说是相当快的。

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

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

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

      将您的时间与上述平均值进行比较:每秒 50,000 => 这应该需要 2m 00 秒。这仅比您的时间快一点。

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

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

      在下一段中有一些提示,您可以尝试加快插入速度:

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

      我一直认为 SQLite 是为“简单的事情”而设计的,600 万条记录对我来说似乎是一些像 MySQL 这样的真实数据库服务器的工作。

      在 SQLite 中计算具有如此多记录的表中的记录可能需要很长时间,仅供参考,而不是使用 SELECT COUNT(*),您始终可以使用非常快的 SELECT MAX(rowid),但不是如果您要删除该表中的记录,那就太准确了。

      编辑。

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

      【讨论】:

      • 我在一个简单的数据库中也遇到了这么多数据的问题,但我喜欢它的嵌入式特性,唯一的数据查询将是 SELECT * FROM TABLE WHERE Id = 'myID'。目前我唯一不知道的问题是,如果我尝试每 100 毫秒执行一次 SELECT,它将如何应对
      【解决方案4】:

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

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

      我还想知道 600 万行的事务是否太过分了 - 您可以更改代码以尝试不同的事务大小吗?说 100、1000、10000、100000?有没有“甜蜜点”?

      【讨论】:

      • @Jon:很奇怪。我刚刚插入了 1m 行(两次都插入到空数据库中):101 秒,索引到位,64 秒没有,再加上 16 秒作为索引。所以大约快 20%。啊。但是,虽然插入时间销售与行数大致呈线性关系,但索引创建似乎并非如此。因此,可能介于我的百万行和您的 600 万行之间,随着您的使用,索引会变得更便宜。 B-tree 索引可能更糟 O(n) - 或者至少,它在 SQLite 中看起来如此。 :-(
      • 也许您是否有区别:1)在插入之后创建索引,但在“插入”事务内部 2)在提交“插入”事务之后创建索引。也许你们俩都以另一种方式做到了,因此结果不同。只是一个理论。
      • 我将所有插入作为单个事务运行,提交,然后创建索引,这似乎是最佳的。有趣的事情似乎是插入时间(无论如何最多 6m 行)或多或少地线性扩展,而索引创建时间却没有。因此,对于大量数据,走反直觉的路线可能会更有效(需要更多数据)。
      猜你喜欢
      • 1970-01-01
      • 2014-06-11
      • 2019-09-20
      • 1970-01-01
      • 2012-02-06
      • 2011-12-07
      • 1970-01-01
      • 2013-02-12
      • 1970-01-01
      相关资源
      最近更新 更多