簡體   English   中英

批量插入在Azure SQL Server中無法正常工作

[英]Bulk insert is not working properly in Azure SQL Server

我無法使用C#webapi將大量數據插入Azure SQL Server數據庫

考慮

我想在SQL中插入60K>數據。 在我的本地SQL服務器中沒有問題,但在Azure SQL中它的連接超時

我的方法:(所有都在本地sql服務器上工作,但在Azure sql server中沒有)

1)嘗試使用EF逐個插入記錄(10000次約10分鍾,大部分超時)

2)嘗試使用批量插入擴展和EF 3)在SqlBulkCopy中嘗試

4)嘗試增加連接字符串中的連接超時

5)嘗試在Dbcontext中增加命令時間。

異常StackTrace

Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
System.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error)
   at System.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync()
   at System.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket()
   at System.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer()
   at System.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlBulkCopy.RunParser(BulkCopySimpleResultSet bulkCopyHandler)
   at System.Data.SqlClient.SqlBulkCopy.CopyBatchesAsyncContinuedOnSuccess(BulkCopySimpleResultSet internalResults, String updateBulkCommandText, CancellationToken cts, TaskCompletionSource`1 source)
   at System.Data.SqlClient.SqlBulkCopy.CopyBatchesAsyncContinued(BulkCopySimpleResultSet internalResults, String updateBulkCommandText, CancellationToken cts, TaskCompletionSource`1 source)
   at System.Data.SqlClient.SqlBulkCopy.CopyBatchesAsync(BulkCopySimpleResultSet internalResults, String updateBulkCommandText, CancellationToken cts, TaskCompletionSource`1 source)
   at System.Data.SqlClient.SqlBulkCopy.WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet internalResults, CancellationToken cts, TaskCompletionSource`1 source)
   at System.Data.SqlClient.SqlBulkCopy.WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletionSource`1 source)
   at System.Data.SqlClient.SqlBulkCopy.WriteToServerInternalAsync(CancellationToken ctoken)
   at System.Data.SqlClient.SqlBulkCopy.WriteRowSourceToServerAsync(Int32 columnCount, CancellationToken ctoken)
   at System.Data.SqlClient.SqlBulkCopy.WriteToServer(DataTable table, DataRowState rowState)

是否有任何解決方案或Azure中的任何配置要更改?

更新

用於批量插入的代碼

  using (var dbConnection = new DBModel().Database.Connection as SqlConnection)
                {
                    dbConnection?.Open();
                    using (var sqlBulkCopy = new SqlBulkCopy(dbConnection))
                    {
                        try
                        {
                            /* ColumnMapping
                             * Column is mapped to DB Column to DataTable Column
                             *
                             */
                            sqlBulkCopy.EnableStreaming = true;
                            sqlBulkCopy.BulkCopyTimeout = 500;
                            sqlBulkCopy.DestinationTableName = "LogTable";
                            //dt is object of the Datatable
                            sqlBulkCopy.WriteToServer(dt);
                        }
                        catch (Exception ex)
                        {

                        }
                    }


                }

我建議你將sqlBulkCopy.BatchSize設置為合理的數量,而不是一次性插入所有內容。 根據您要插入的數據,嘗試從10.000開始,向上或向下工作,直到您對性能滿意為止。

編輯以獲得一些額外的說明:當您考慮批量大小時,您需要考慮到SqlBulkCopy不僅需要插入數據,還需要讀取和發送它 - 最后一部分可能是它在本地工作的原因SQL服務器,但不在Azure上 - 它還意味着,如果您正在使用大型數據集,則需要使用較低的批處理大小或相當高的BulkCopyTimeout設置,以允許每個批處理完成之前達到超時限制。

您可以在本文中閱讀有關批量大小的更多信息。 SqlBulkCopy的推薦批量大小是多少?

其他選擇:
我正在讀這篇文章,它可能只是因為你的插入到達了一個關鍵的DTU(數據庫事務處理單元,基本上是服務器組合資源的度量)使用點。

性能級別經過校准和管理,以提供運行數據庫工作負載所需的資源,達到所選服務層/性能級別允許的最大限制。 如果您的工作負載達到了CPU / Data IO / Log IO限制之一的限制,您將繼續以最大允許級別接收資源,但您可能會看到查詢的延遲增加 這些限制不會導致任何錯誤,只會導致工作負載減慢 ,除非減速變得非常嚴重以至於查詢開始超時

取自以下鏈接: https//azure.microsoft.com/da-dk/blog/azure-sql-database-introduces-new-near-real-time-performance-metrics/
嘗試在監視DTU使用情況時再次啟動副本,並查看它是否在100(長期)時間內處於100%狀態。 如果是這種情況,您可能需要提高數據庫的定價層級別。

我在過去兩天一直在處理批量插入,這里是一個通用批量插入類,允許你排除一些列,如果發現DbGeography屬性,它將它轉換為SqlGeography:

 public class BulkInsert<T> where T : class
{
    #region Fields

    private readonly LoggingService _logger = new LoggingService(typeof(BulkInsert<T>));
    private string _connectionString;
    private string _tableName;
    private IEnumerable<string> _excludedPropertyNames;
    private int _batchSize;
    private IEnumerable<T> _data;
    private DataTable _dataTable;

    #endregion

    #region Constructor

    public BulkInsert(
        string connectionString,
        string tableName,
        IEnumerable<T> data,
        IEnumerable<string> excludedPropertyNames,
        int batchSize = 1000)
    {
        if (string.IsNullOrEmpty(connectionString)) throw new ArgumentNullException(nameof(connectionString));
        if (string.IsNullOrEmpty(tableName)) throw new ArgumentNullException(nameof(tableName));
        if (data == null) throw new ArgumentNullException(nameof(data));
        if (batchSize <= 0) throw new ArgumentOutOfRangeException(nameof(batchSize));

        _connectionString = connectionString;
        _tableName = tableName;
        _batchSize = batchSize;
        _data = data;
        _excludedPropertyNames = excludedPropertyNames == null ? new List<string>() : excludedPropertyNames;
        _dataTable = CreateCustomDataTable();
    }

    #endregion

    #region Public Methods

    public void Insert()
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            SqlTransaction transaction = connection.BeginTransaction();

            using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default | SqlBulkCopyOptions.KeepIdentity, transaction))
            {
                bulkCopy.BatchSize = _batchSize;
                bulkCopy.DestinationTableName = _tableName;

                // Let's fix tons of mapping issues by
                // Setting the column mapping in SqlBulkCopy instance:
                foreach (DataColumn dataColumn in _dataTable.Columns)
                {
                    bulkCopy.ColumnMappings.Add(dataColumn.ColumnName, dataColumn.ColumnName);
                }

                try
                {
                    bulkCopy.WriteToServer(_dataTable);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex.Message);
                    transaction.Rollback();
                    connection.Close();
                }
            }

            transaction.Commit();
        }
    }

    #endregion

    #region Private Helper Methods

    private DataTable CreateCustomDataTable()
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
        var table = new DataTable();
        foreach (PropertyDescriptor prop in properties)
        {
            // Just include the not excluded columns
            if (_excludedPropertyNames.All(epn => epn != prop.Name))
            {                  
                if (prop.PropertyType.Name == "DbGeography")
                {
                    var type = typeof(SqlGeography);
                    table.Columns.Add(prop.Name, type);
                }
                else
                {
                    table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
                }
            }
        }
        foreach (T item in _data)
        {
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
            {
                // Just include the values in not excluded properties 
                if (_excludedPropertyNames.All(epn => epn != prop.Name))
                {
                    if (prop.PropertyType.Name == "DbGeography")
                    {                           
                        row[prop.Name] = SqlGeography.Parse(((DbGeography)prop.GetValue(item)).AsText()).MakeValid();
                    }
                    else
                    {
                        row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
                    }
                }
            }
            table.Rows.Add(row);
        }
        return table;
    }

    #endregion

}

好吧,它必須像以下一樣使用:

var myEntityBulk = new BulkInsert<MyEntity>(
    _mYConnectionString,
     "MyEntities", 
      myEntities, 
      new[] { "ObjectState","NavigationPropertyOne", "NavigationPropertyTwo" }
);
myEntityBulk.Insert();

我希望它有所幫助,我很確定它會...我昨天在幾分鍾內就與它和M實體進行了大量合作。

首先,如果您只是將記錄插入數據庫,請確保是否可以連接到Azure SQL數據庫並執行沒有超時錯誤的操作。

其次,請檢查是否通過使用ObjectContext上的CommandTimeout屬性覆蓋默認超時值,並且可以嘗試為BulkCopyTimeout屬性設置0(表示無限制)。

此外,在批量插入時,請確保它是否達到Azure SQL數據庫的限制,您可以嘗試更改數據庫的服務層和性能級別

使用SqlBulkCopy時,依賴於Web服務器和Azure SQL服務器之間的Route-Trip-Time(距離),您需要注意兩個參數:

  1. BatchSize(對我來說,我將批量大小設置為500)

  2. BulkCopyTimeOut:默認為30秒(我設置為60秒)

     using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection)) { bulkCopy.BatchSize = array.Length; bulkCopy.DestinationTableName = tempTableName; bulkCopy.BulkCopyTimeout = 60;...} 

通過這種配置,我從EventStore到我的Projection MicroService獲取20K +事件的整個過程,然后microService更新/將這些20K事件寫入數據庫花了大約15分鍾(取決於事件存儲和MicroService之間的RTT路由行程時間) ,以及Microservice和數據庫之間)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM