Замедление выполнения объемных вставок - PullRequest
0 голосов
/ 15 мая 2018

У нас есть база данных SQL Azure, которая находится на ценовом уровне S1. Наш сайт очень сильно кешируется, поэтому обращения к базе данных абсолютно минимальны. Среднее использование DTU составляет всего ~ 1,5%, это здорово, так как наши затраты на БД - это доля того, что было на нашем старом сайте (£ 20 p / m против £ 400 p / m!)

На сайте, однако, у нас есть небольшие скрипты, которые требуют вставки ~ 100 тыс. Записей или около того (пользовательские уведомления о том, когда кто-то выполняет действие, такое как создание нового учебного пособия).

Когда это срабатывает, всплеск DTU на 100% в течение 3-5 минут.

Скрипт - это просто цикл, который вызывает вставку:

using(var db = new DBContext())
{
    foreach(var userID in userIDs)
    {
        db.ExecuteCommand(
        "INSERT INTO UserNotifications " +
        "(ForUserID, Date, ForObjectTypeID, ForObjectID, TypeID, Count, MetaData1) 
        VALUES ({0}, {1}, NULL, {2}, {3}, {4}, {5}, {6})",
        userID, DateTime.Now.ToUniversalTime(), forObjectID, (byte)type, 1, metaData1.Value
        );
    }
}
  • Есть ли более быстрый способ вставки, чем этот?
  • Кроме того, что может быть лучшим способом замедлить выполнение этого сценария, чтобы использование DTU не подавляло все?

Ответы [ 2 ]

0 голосов
/ 15 мая 2018

Вы делаете одну строку на вставку - это неэффективно.

A TVP похож на обратный считыватель данных и эффективен.

Более низкая технология состоит в том, чтобы вставлять 900 строк за раз (1000 - максимум). Одно это, вероятно, в 400 раз более эффективно.

StringBuilder sb = new StringBuilder();
string insert = "INSERT INTO UserNotifications " +
                "(ForUserID, Date, ForObjectTypeID, ForObjectID, TypeID, Count, MetaData1) " +  
                "VALUES ";
sb.AppendLine(insert);
int count = 0;
using(var db = new DBContext())
{        
    foreach(var userID in userIDs)
    {
        sb.AppendLine(string.Format(({0}, {1}, NULL, {2}, {3}, {4}, {5}, {6}), ",
                      userID, DateTime.Now.ToUniversalTime(), forObjectID, (byte)type, 1, metaData1.Value);
        count++;
        if (count = 990) 
        {
            db.ExecuteCommand(sb.ToString());
            count = 0;
            sb.Clear();
            sb.AppendLine(insert); 
            //can sleep here to throttle down cpu 
        }            
    }
    if (count > 0) 
    {
        db.ExecuteCommand(sb.ToString());
    }
}
0 голосов
/ 15 мая 2018

Вместо вставки сущности по сущности вы можете сделать вставку из 100 сущностей одновременно, упаковав сущности в JSON и написав процедуру хранения, использующую ее, как в этом примере:

INSERT INTO [dbo].[AISecurityLogs]
    ([IpAddress], [TimeRange], [Requests], [LogId])
    SELECT *, LogId = @logId
    FROM OPENJSON ( @json )  
    WITH (   
        IpAddress varchar(15) '$.IpAddress',  
        TimeRange DATETIME '$.TimeRange',  
        Requests int '$.Requests'
     )

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

...