代码之家  ›  专栏  ›  技术社区  ›  Jeroen Huinink

为什么在没有事务的情况下插入一百万条记录比在事务内部慢?

  •  3
  • Jeroen Huinink  · 技术社区  · 15 年前

    我正在使用.NET 3.5对SQL Server进行一些性能测试。我正在插入100万张唱片。当我将它包装在事务(序列化、repeatabelread或readuncommited)中时,它在我的系统上运行不到80秒。当我删除事务时,它大约在300秒内运行。我希望不使用事务是将行插入数据库的最快方法,因为DBMS不需要考虑潜在的回滚。这里发生了什么?这对于SQL Server、SQL Server ADO.NET提供程序、ADO.NET(一般情况下)和DBMS(一般情况下)是典型的吗?

    我有iSeries/DB2数据库的背景。在DB2中,在获得承诺控制和事务之前,必须启用日志记录,而且日志记录相对昂贵。

    我真正想做的是比较sqlcommand插入和实体框架插入,但是我对这些结果感到非常惊讶,所以我想先看看这里发生了什么。

    下面是我用来运行A测试的代码。当我运行下面的代码时,大约需要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 回复  |  直到 15 年前
        1
  •  8
  •   Remus Rusanu    15 年前

    原木冲洗。

    如果没有显式事务,则每个语句(即insert)启动的隐式事务必须提交。在将日志中的数据写入磁盘之前,commit无法返回,这意味着每个insert语句都必须等待日志磁盘写入操作。

    显式事务必须只在发出commit语句时等待,到那时,每个完整的日志页都已提交,最后一个日志页可能包含多个插入,因此写入成本是分摊的。

    更新:

    您可以在性能计数器中验证日志刷新时间: http://msdn.microsoft.com/en-us/library/ms189883.aspx :

    • 日志刷新等待时间 刷新日志的总等待时间(毫秒)。
    • 日志刷新等待/秒 每秒等待日志刷新的提交数。
    • 原木冲洗/秒 每秒刷新日志的次数。
        2
  •  0
  •   Sergey Mirvoda    15 年前

    因为每个命令(如果事务没有显式设置)都是用事务隐式包装的,即您有1M事务。至少对于sqlite

        3
  •  0
  •   David M    15 年前

    如果您不是事务性的,它必须获取并释放每个插入的锁。通过该事务,它可以为多个插入保持一个锁打开。更少的开销。

        4
  •  0
  •   A-K    15 年前