Почему вставка 1М записей медленнее без транзакции, чем внутри транзакции? - PullRequest
3 голосов
/ 25 июня 2009

Я делаю некоторые тесты производительности, используя .Net 3.5 против SQL Server. Я делаю вставку из 1 миллиона записей. Когда я обертываю это внутри транзакции (сериализуемой, RepeatabelRead или ReadUncommited), она выполняется в моей системе менее чем за 80 секунд. Когда я удаляю транзакцию, она запускается примерно через 300 секунд. Я ожидаю, что использование транзакции не будет самым быстрым способом вставки строк в базу данных, поскольку СУБД не нужно учитывать потенциальный откат. Что здесь происходит? Это типично для SQL Server, поставщика ADO.Net для SQL Server, ADO.Net в целом, СУБД в целом?

У меня есть опыт работы в базах данных iSeries / DB2. В DB2 вам нужно включить ведение журналов, прежде чем вы сможете получить контроль фиксации и транзакции, и журналирование относительно дорого.

Я действительно хотел сравнить вставки SqlCommand со вставками Entity Framework, но я был настолько удивлен этими результатами, что хотел сначала выяснить, что здесь происходит.

Ниже кода, который я использую для запуска теста. Когда я запускаю приведенный ниже код, это занимает около 74 секунд (измеряется между журналом AtStart и строками журнала AtEnd)

using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
    sqlConnection.Open();
    SqlCommand deleteCommand = new SqlCommand("DELETE FROM LockTest");
    deleteCommand.Connection = sqlConnection;
    deleteCommand.ExecuteNonQuery();

    using (SqlTransaction transaction = sqlConnection.BeginTransaction(System.Data.IsolationLevel.Serializable))
    {
        try
        {
            if (DEBUG) LOG.Debug("AtStart");

            SqlCommand insertCommand = new SqlCommand();
            insertCommand.Connection = sqlConnection;
            insertCommand.Transaction = transaction;

            insertCommand.CommandText = "INSERT INTO LockTest (Id, Name, Description, Type) "  + 
                "VALUES (@id, @name, @description, @type)";
            SqlParameter idParameter = new SqlParameter("@id", System.Data.SqlDbType.UniqueIdentifier);
            insertCommand.Parameters.Add(idParameter);
            SqlParameter nameParameter = new SqlParameter("@name", System.Data.SqlDbType.NVarChar, 50);
            insertCommand.Parameters.Add(nameParameter);
            SqlParameter descriptionParameter = new SqlParameter("@description", System.Data.SqlDbType.NVarChar, Int32.MaxValue);
            insertCommand.Parameters.Add(descriptionParameter);
            SqlParameter typeParameter = new SqlParameter("@type", System.Data.SqlDbType.NChar, 20);
            insertCommand.Parameters.Add(typeParameter);

            insertCommand.Prepare();

            for (int i= 0; i < 1000000; i++)
            {
                Guid g = Guid.NewGuid();
                string s = g.ToString();
                insertCommand.Parameters["@id"].Value = g;
                insertCommand.Parameters["@name"].Value = s;
                insertCommand.Parameters["@description"].Value = DateTime.UtcNow.Ticks.ToString();
                insertCommand.Parameters["@type"].Value = "test";
                insertCommand.ExecuteNonQuery();
            }
            transaction.Commit();
        }
        catch
        {
            transaction.Rollback();
            throw;
        }

    }
    sqlConnection.Close();
}
if (DEBUG) LOG.Debug("AtEnd");

Ответы [ 4 ]

8 голосов
/ 25 июня 2009

Журнал заподлицо.

При отсутствии явных транзакций неявные транзакции, запускаемые каждым оператором (т. Е. INSERT), должны фиксироваться. Коммит не может вернуться, пока данные в журнале не будут записаны на диск, что означает, что каждый оператор INSERT должен ждать операции записи на диск журнала.

Явные транзакции должны ждать только после выполнения оператора COMMIT, и к тому времени каждая полная страница журнала уже была отправлена, а последняя страница журнала, вероятно, содержит несколько INSERT, поэтому стоимость записи амортизируется.

Обновление:

Вы можете проверить время сброса журнала в счетчиках производительности: http://msdn.microsoft.com/en-us/library/ms189883.aspx:

  • Время ожидания очистки журнала Общее время ожидания (в миллисекундах) для очистки журнала.
  • Ожидание сброса журнала / сек. Число коммитов в секунду, ожидающих сброса журнала.
  • Журналы сбрасывает в секунду Количество журналов сбрасывает в секунду.
0 голосов
/ 28 июня 2009

Для быстрой загрузки используйте Use SqlBulkCopy:

http://www.sqlteam.com/article/use-sqlbulkcopy-to-quickly-load-data-from-your-client-to-sql-server

0 голосов
/ 25 июня 2009

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

0 голосов
/ 25 июня 2009

Потому что каждая команда (если транзакция не установлена ​​явно) обернута транзакцией неявно, т.е. у вас есть 1M транзакций. По крайней мере, для sqLite

...