Массовая вставка Sqlite со свежей транзакцией пакета

#c# #sqlite #.net-core

#c# #sqlite #.net-ядро

Вопрос:

Мне нужно заполнить таблицу Sqlite предварительно вычисленными значениями. Только одна таблица с текущими ~ 20 строками.

После того, как я повозился день или два, мне удалось получить подход 1, работающий с массовыми вставками, который отлично работает для «меньшего количества записей» (5_000_000 записей), и это довольно быстро. К сожалению, подход 1 зависает где-то на 139_000_000 записей, и к настоящему времени итератор доставит ~ 200_000_000 записей. Консольное приложение не останавливается и не генерирует исключение, которым я мог бы поделиться здесь.

И мне интересно, куда идут данные, потому что объем памяти значительно не увеличивается и файл базы данных не записывается (это происходит при фиксации в конце).

Мой подход 1:

 public static void SqliteBulkInsert()
{
    CustomIterator citer = new CostumIterator();

    using (var connection = new SqliteConnection("Filename=Iterations.db"))
    {
        connection.Open();

        using (var transaction = connection.BeginTransaction())
        {
            var command = connection.CreateCommand();
            command.Transaction = transaction;

            command.CommandText = @"INSERT INTO iterations (V1, V2) VALUES ($V1, $V2)";

            var p1 = command.CreateParameter();
            p1.ParameterName = "$V1";
            command.Parameters.Add(p1);

            var p2 = command.CreateParameter();
            p2.ParameterName = "$V2";
            command.Parameters.Add(p2);

            long i = 0;

            while (citer.Next(out byte v1, out byte v2))
            {
                p1.Value = v1;
                p2.Value = v2;

                command.ExecuteNonQuery();

                if (i % 10_000 == 0) Console.WriteLine(i);
            }

            transaction.Commit();
        }

        Console.WriteLine("done");
        connection.Close();
    }
}
 

Во втором подходе я попытался создать новую транзакцию для каждого пакета 1_000_000. Но как бы я ни старался, я получал разные исключения, такие как «транзакция команды не принадлежит текущему соединению» и так далее.

Когда я просто добавляю коммит в пакет, как в

 if (i % 10_000 == 0) 
{ 
  transaction.Commit(); 
  Console.WriteLine(i);
}
 

Я получаю:

 System.InvalidOperationException
  HResult=0x80131509
  Message=The transaction object is not associated with the same connection object as this command.
  Source=Microsoft.Data.Sqlite
  StackTrace:
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteNonQuery()
   at DevTryOuts.Program.PopulateSqliteCombinationsBulkInsert2() in A:DevTryOutsProgram.cs:line 568
   at DevTryOuts.Program.Main(String[] args) in A:DevTryOutsProgram.cs:line 115
 

Любой намек на то, как я мог бы правильно реализовать новую транзакцию для каждого пакета?


Были некоторые ошибки:

  1. Итератор не генерирует 200 миллионов записей: он генерирует только около 140 миллионов
  2. Поэтому он достигает фиксации транзакции (по этой причине последняя запись журнала показывает индекс 139 миллионов
  3. Он «зависает» (включая полное замораживание ПК) во время записи записей на диск
  4. Он зависал из-за нехватки места на диске (~ 50 ГБ базы данных) и работал при выборе другого диска с большим количеством свободного места на диске.

Комментарии:

1. В SQLite нет массовых операций. Это встроенная база данных, в ней нет отдельного сервера и нет необходимости в операциях массовой вставки. Единственное, что вы можете исключить, это отдельные записи журнала — для этого и предназначена явная транзакция. Какие фактические исключения вы получаете? Если что-то работает для 10 строк, оно работает для 10 строк.

2. Кроме того, если данные поступают в формате CSV, вы можете открыть их непосредственно через SQLite, как если бы это была база данных SQLite, или получить к ней доступ как к виртуальной таблице и скопировать данные в свою собственную базу данных. Вы могли бы использовать sqlite3 и использовать, например .import c:/sqlite/city.csv cities , для импорта данных в новую таблицу.

3. Данные поступают не из CSV. Он вычисляется с использованием итератора. Если что-то работает для 10 строк, оно работает для 10 строк. Ну, может быть. В настоящее время я сталкиваюсь с доказательством, что это не обязательно работает с 200 миллионами строк. Мои ожидания — это просто заполнение таблицы. Я начал с EF Core, но это было слишком медленно и заняло слишком много времени. Итак, я работал над подходом 1, чтобы повысить скорость. Я начал с этого фрагмента документа: docs.microsoft.com/en-us/dotnet/standard/data/sqlite /…

4. When I just add a commit to the batch as in в этом проблема. На этом транзакция завершается . Он предназначен только для использования в самом конце. Если вы попытаетесь снова использовать ту же транзакцию, вы получите сообщение об ошибке. Если вы хотите совершать пакетные транзакции, вам следует запускать новую транзакцию для каждого пакета

5. в любом случае вы не опубликовали ни исходное, ни текущее исключение. Полный текст исключения содержит все внутренние исключения и трассировку стека с вызовами, которые привели к ошибке. Вполне возможно, что исключение уже сообщает вам, что транзакция больше не существует