Повышение производительности импорта больших данных в SQLite с помощью C # - PullRequest
17 голосов
/ 11 ноября 2011

Я использую C # для импорта CSV с 6-8 миллионами строк.

Мой стол выглядит так:

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

Я использую System.Data.SQLite для импорта.

В настоящее время выполнение 6 миллионов строк занимает 2 минуты 55 секунд на 32-битной Windows 7, Core2Duo 2,8 ГГц и 4 ГБ ОЗУ. Это не так уж и плохо, но мне просто интересно, сможет ли кто-нибудь найти способ импортировать его быстрее.

Вот мой код:

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 ]

11 голосов
/ 11 ноября 2011

Это довольно быстро для 6 миллионов записей.

Кажется, что вы делаете это правильно, некоторое время назад я читал на sqlite.org, что при вставке записей вам нужно поместить эти вставки в транзакцию, если вы не сделаете этого, ваши вставки будут ограничены только до 60 в секунду! Это связано с тем, что каждая вставка будет обрабатываться как отдельная транзакция, и каждая транзакция должна ожидать полного вращения диска. Вы можете прочитать полное объяснение здесь:

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

На самом деле, SQLite будет легко выполнять 50 000 или более операторов INSERT в секунду на обычном настольном компьютере. Но он будет делать только несколько десятков транзакций в секунду. Скорость транзакции ограничена скоростью вращения вашего дисковода. Транзакция обычно требует двух полных оборотов дискового диска, что на диске 7200 об / мин ограничивает до 60 транзакций в секунду.

Сравнение вашего времени со средним, указанным выше: 50 000 в секунду =>, что должно занять 2 м 00 сек. Который только немного быстрее, чем ваше время.

Скорость транзакции ограничена скоростью диска, потому что (по умолчанию) SQLite фактически ожидает, пока данные действительно не будут безопасно сохранены на поверхности диска, прежде чем транзакция будет завершена. Таким образом, если вы вдруг потеряете питание или ваша ОС выйдет из строя, ваши данные останутся в безопасности. Подробнее читайте об атомарной фиксации в SQLite.

По умолчанию каждый оператор INSERT является собственной транзакцией. Но если вы окружите несколько операторов INSERT с помощью BEGIN ... COMMIT, тогда все вставки будут сгруппированы в одну транзакцию. Время, необходимое для фиксации транзакции, амортизируется по всем вложенным операторам вставки, поэтому время на оператор вставки значительно сокращается.

В следующем абзаце есть подсказка, что вы можете попытаться ускорить вставку:

Другой вариант - запустить PRAGMA синхронно = OFF. Эта команда приведет к тому, что SQLite не будет ждать, пока данные достигнут поверхности диска, что сделает операции записи намного более быстрыми. Но если вы потеряете мощность в середине транзакции, файл базы данных может испортиться.

Я всегда думал, что SQLite был разработан для "простых вещей", 6 миллионов записей мне кажется работой для какого-то реального сервера баз данных, такого как MySQL.

Подсчет записей в таблице в SQLite с таким количеством записей может занять много времени, просто для вашей информации, вместо того чтобы использовать SELECT COUNT (*), вы всегда можете использовать SELECT MAX (rowid), который очень быстрый, но не очень так точно, если вы удаляете записи в этой таблице.

EDIT.

Как сказал Майк Вудхаус, создание индекса после того, как вы вставили записи, должно ускорить все это, что является общим советом в других базах данных, но не может точно сказать, как он работает в SQLite.

5 голосов
/ 11 ноября 2011

Одна вещь, которую вы можете попробовать, - это создать индекс после данных, которые были вставлены - как правило, базы данных гораздо быстрее создают индексы в одной операции, чем обновляют их после каждой вставки (или транзакции) .

Я не могу сказать, что он определенно будет работать с SQLite, но поскольку для его перемещения требуется всего две строки, стоит попробовать.

Мне также интересно, может ли транзакция в 6 миллионов строк зайти слишком далеко - не могли бы вы изменить код, чтобы попробовать разные размеры транзакций? Скажи 100, 1000, 10000, 100000? Есть ли "сладкое пятно"?

1 голос
/ 11 июня 2015

Вы можете выиграть довольно много времени, когда связываете свои параметры следующим образом:

...
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. Таким образом, привязка параметров кажется довольно сложной.Быстрее.Особенно, если у вас много параметров, этот метод может сэкономить довольно много времени.

0 голосов
/ 18 января 2012

Я сделал аналогичный импорт, но я позволил своему коду c # сначала записать данные в csv, а затем запустил утилиту импорта sqlite.Таким образом, я смог импортировать более 300 миллионов записей за 10 минут.

Не уверен, что это можно сделать напрямую из c # или нет.

...