TLDR
Если бы я вызвал EF следующим образом:
dbcontext.Update(newEntityA);
dbContext.Save();
dbcontext.Update(newEntityB);
dbContext.Save();
и все это успешно завершилось бы за <1 миллисекунду, сколько операторов INSERT он выполнил бы?Две вставки с параметрами X каждая или одна вставка с параметрами 2X и строкой VALUES, которая была <code>( (...), (...) )?
Если первая вставка не удалась из-за, например, нарушения PK, это изменит ответ?
Длинная версия
У меня очень простое приложение, которое перемещает данные из RabbitMQ в базу данных SQL Azure.Приложение выглядит примерно так (я убрал большинство вызовов установки / регистрации):
class Program
{
private static IConfigurationRoot _configuration;
private static IConsumer _consumer;
private static ConsumerDbContext _dbContext;
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
_configuration = builder.Build();
_dbContext = new ConsumerDbContext(...configblah...);
_consumer = new RabbitMqConsumer(...configblah...);
_consumer.Connect();
_consumer.ReceivedMessage += ConsumerOnReceivedMessage;
}
private static void ConsumerOnReceivedMessage(object sender, MessageArgs messageArgs)
{
try{
var model = JsonConvert.DeserializeObject<DrawingModel>(messageArgs.Message.ToString());
if (model != null)
{
var drawing = _dbContext.Drawings.FirstOrDefault(x => x.Id == model.DrawingId);
if (drawing != null)
{
if (drawing.DrawingCommandModel != null)
{
var command = new DrawingCommandEntity(model.DrawingCommandModel);
command.Id = model.DrawingCommandId;
_dbContext.Add(command);
}
drawing.LastExecutedCommandId = model.DrawingCommandId;
drawing.CurrentState = model.DrawingState;
_dbContext.Update(drawing);
_dbContext.SaveChanges();
}
_logger.LogInformation("success ...");
}
}
catch (Exception e)
{
_logger.LogInformation("fail .....");
}
}
}
По сути, RMQ содержит JSON, который описывает текущее состояние векторного чертежа, и команду, которая привелав текущем состоянии.В Drawing есть много DrawingCommand, а Drawing.CurrentState является объединением / слиянием нескольких DrawingCommand.Команда DrawingCommand имеет вид "нарисовать зеленую линию шириной 5px от 0,0 до 10,10" , и сами команды могут иметь историю изменений - если пользователь меняет зеленый цвет на синий, тоИзменение обрабатывается как две команды рисования - сначала команда была создана как зеленая, а затем, после того, как она была изменена на синюю, так что новая команда рисования, такая как "рисует синюю линию шириной 5px от 0,0 до 10,10" написано.
Это позволяет отменить / повторить, поэтому CurrentState чертежа можно представить как последний набор уникальных DrawingCommands.Все изменения, которые когда-либо выполняются в списке DrawingCommand чертежа, сохраняются, будь то добавление, удаление или изменение сущностей DrawingCommand
JSON полностью рассчитывается в другом месте;несколько систем отправляют DrawingCommand JSON в центральный агрегатор, агрегатор поддерживает текущее состояние чертежа и всех DrawingCommand, которые его составляют, и отправляет результаты в RMQ.RMQ используется для объединения работы, чтобы одно приложение (это приложение) могло подключиться к Azure и загрузить данные в БД.Это означает, что RMQ уже содержит все направляющие первичного ключа для чертежа и объектов чертежа, и буквально единственное, что делает это приложение, - это зарезервировать JSON для объекта DB и вставить его.
Он делает это так, потому что существует ограничение на количество соединений, которые мы можем установить с базой данных Azure, и объединение вещей через очередь - лучший способ уменьшить нагрузку и работать в пределах лимита соединений в Azure
Итак, к проблеме:
Недавно мы столкнулись с ситуацией, когда сообщения накапливались в RMQ, и потребитель их не обрабатывал.После изменения уровня ведения журнала на минимум информации (предоставлено, запись исключения с уровнем информации невелика), а не на «Ошибка», я нажимаю кнопку на портале управления Azure, чтобы перезапустить службу.
Просмотр журнала (входя в хранилище BLOB-объектов Azure (новый файл, созданный в тот день), стало очевидно, что дубликат GUID PK вошел в очередь и прерывает запрос INSERT, сформированный EF.К моему удивлению, при извлечении логов запросы, которые формировал EF, были огромными:
date,level,message
2019-09-20T11:52:02,Inf,fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
2019-09-20T11:52:02,Inf," Failed executing DbCommand (2,689ms) [Parameters= .... 1.4 megabytes of data snipped ... ]
2019-09-20T11:52:02,Inf, SET NOCOUNT ON;,
2019-09-20T11:52:02,Inf," INSERT INTO [DrawingCommands] ([Id], [CommandType], [CreatedDate], [CurrentState], [CustomerId], [ObjectsIds], [PreviousState], [ToolType], [DrawingId])"
2019-09-20T11:52:02,Inf," VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8),",
2019-09-20T11:52:02,Inf," (@p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17),",
2019-09-20T11:52:02,Inf," (@p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26),",
2019-09-20T11:52:02,Inf," (@p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35),",
2019-09-20T11:52:02,Inf," (@p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44),",
2019-09-20T11:52:02,Inf," (@p45, @p46, @p47, @p48, @p49, @p50, @p51, @p52, @p53),",
2019-09-20T11:52:02,Inf," (@p54, @p55, @p56, @p57, @p58, @p59, @p60, @p61, @p62),",
2019-09-20T11:52:02,Inf," (@p63, @p64, @p65, @p66, @p67, @p68, @p69, @p70, @p71),",
2019-09-20T11:52:02,Inf," (@p72, @p73, @p74, @p75, @p76, @p77, @p78, @p79, @p80),",
2019-09-20T11:52:02,Inf," (@p81, @p82, @p83, @p84, @p85, @p86, @p87, @p88, @p89),",
2019-09-20T11:52:02,Inf," (@p90, @p91, @p92, @p93, @p94, @p95, @p96, @p97, @p98),",
2019-09-20T11:52:02,Inf," (@p99, @p100, @p101, @p102, @p103, @p104, @p105, @p106, @p107),",
...
2019-09-20T11:52:02,Inf," (@p2088, @p2089, @p2090, @p2091, @p2092, @p2093, @p2094, @p2095, @p2096);",
1.4 мегабайта данных составляют строку параметров, более 2096 параметров
Мне любопытночтобы узнать, как получилось, что EF пытается сохранить примерно 232 команды рисования за один раз.Мне также любопытно, как получается, что это самое первое в журнале.
Я подозреваю, что отслеживаемые объекты добавляются с помощью вызова Update()
и сохраняются в вызове Save()
.,Если произойдет сбой Save()
, то объект останется несохраненным в списке отслеживаемых объектов, и когда другое сообщение очереди будет обработано и добавлено с помощью Update()
, тогда попытка Save()
будет предпринята снова - на этот раз в списке есть два объекта, поэтому9 параметров станут 18 .. И так далее, пока граф сохраняемых объектов не станет огромным ..
Это так?
Есть какие-нибудь мысли о том, почему я не вижу последовательности сообщений об ошибках, в которых количество параметров многократно увеличивалось вплоть до 2096 года?Буквально первая запись в журнале после перенастройки минимального уровня регистрации в информацию имела 2096 параметров.
Существует ли вероятность того, что изменение уровня журнала было обнаружено немедленно, но запрос от панели управления Azure на перезапуск службы был обработан позже?Сразу после последнего массивного запроса в журналах я вижу, что «поток прерывается», затем служба возвращается в нормальное состояние, вставляя по одному объекту за раз.Я думаю, что либо EF объединяет запись в db, поэтому он не делает Save()
немедленно, если другой Save()
следует в течение очень короткого промежутка времени ... или что изменение уровня журнала отражается немедленно, поэтому первые записи журналабыли только те последние секунды, когда приложение все еще выполняло свой старый список объектов.При перезапуске приложения был удален весь набор несохраненных объектов, что позволило продолжить работу в обычном режиме