Когда использовать МоментВремени, Границу и КонецДня при получении остатков в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Правильное позиционирование на временной оси для получения корректных остатков в платформе «1С:Предприятие» — одна из важнейших задач разработчика. Неверный выбор между объектами МоментВремени, Граница или стандартной Датой (например, функцией КонецДня) может привести не только к логическим ошибкам в учете, но и к серьезному падению производительности всей системы.

В этой статье мы подробно разберем, как эти механизмы работают «под капотом», проанализируем влияние каждого подхода на СУБД и выясним, как писать оптимальный код в соответствии с современными стандартами фирмы «1С».

Как платформа 1С рассчитывает остатки «под капотом»

Чтобы понять, почему использование тех или иных объектов влияет на скорость работы системы, давайте детально разберем физическую структуру хранения данных в СУБД для регистров накопления. Платформа использует две основные таблицы:

  1. Таблица движений: хранит детальные записи (обороты) по каждому документу с указанием конкретного периода (секунды) и регистратора.
  2. Таблица итогов: хранит уже рассчитанные остатки на фиксированные моменты времени. По умолчанию платформа рассчитывает и хранит итоги на начало каждого месяца (первая секунда месяца, например, 01.04.2019 00:00:00).

Выясним, как СУБД рассчитывает остаток, когда мы запрашиваем его на произвольную дату (например, на 15.04.2019 14:30:15). Система не считает все движения с самого начала ведения учета. Алгоритм выглядит следующим образом:

Таким образом, чем ближе запрашиваемая дата к точке хранения итогов, тем меньше движений нужно «досчитывать» системе и тем быстрее выполняется запрос.

Философия «дискретной секунды» в 1С

В платформе 1С тип данных Дата имеет точность только до секунды. Из-за этого возникает важная логическая коллизия: секунда в системе — это не абстрактная точка, а интервал времени, внутри которого может быть записано множество документов. Каждый документ внутри этой секунды имеет свою уникальную позицию (момент времени).

При обращении к виртуальной таблице остатков по простой Дате (например, 31.12.2019 23:59:59), платформа всегда возвращает остатки на начало этой секунды (то есть исключая саму эту секунду). Если в эту же секунду 23:59:59 были записаны документы, их движения не попадут в полученный остаток.

Чтобы решить эту проблему, разработчики используют специальные объекты позиционирования: МоментВремени и Граница.

МоментВремени vs Граница: в чем принципиальная разница?

Рассмотрим подробнее назначение и механику работы этих двух объектов:

Проанализируем классические сценарии применения этих объектов.

Сценарий 1. Контроль остатков при проведении документа (оперативный контроль)

Когда мы проводим, например, документ РасходнаяНакладная, нам нужно проверить, достаточно ли товара на складе. При этом движения самого этого документа еще не должны учитываться (иначе мы получим искаженные данные). Для этой задачи мы обязаны передавать в параметры виртуальной таблицы остатков именно МоментВремени текущего документа:


// Пример запроса для оперативного контроля остатков при проведении
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТоварыНаСкладахОстатки.Номенклатура,
    |   ТоварыНаСкладахОстатки.КоличествоОстаток КАК Остаток
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";

Запрос.УстановитьПараметр("Период", ЭтотОбъект.МоментВремени());
Запрос.УстановитьПараметр("Номенклатура", ВыбранныйТовар);
РезультатЗапроса = Запрос.Выполнить();

Сценарий 2. Получение остатков на конец отчетного периода (например, дня или месяца)

Если нам нужно получить остатки на конец дня (например, на 31.03.2019 23:59:59) и мы хотим гарантированно учесть все документы, которые могли быть записаны в последнюю секунду дня, использование обычной даты вернет неполные данные. В этом случае мы должны использовать объект Граница:


// Использование Границы для получения остатков на конец дня
КонечнаяДата = КонецДня(РабочаяДата);
ПараметрГраница = Новый Граница(КонечнаяДата, ВидГраницы.Включая);

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ВзаиморасчетыОстатки.Физлицо,
    |   ВзаиморасчетыОстатки.СуммаОстаток
    |ИЗ
    |   РегистрНакопления.Взаиморасчеты.Остатки(&МомВрем, Физлицо = &Физик) КАК ВзаиморасчетыОстатки";

Запрос.УстановитьПараметр("МомВрем", ПараметрГраница);
Запрос.УстановитьПараметр("Физик", ВыбранноеФизЛицо);
РезультатЗапроса = Запрос.Выполнить();

Почему МоментВремени и Граница могут сильно тормозить базу?

Использование сложных объектов позиционирования имеет свою цену. На уровне СУБД передача объекта МоментВремени или Граница превращается в комплексный SQL-запрос с дополнительными подзапросами и фильтрами по уникальным идентификаторам документов (GUID).

Поскольку СУБД вынуждена позиционироваться внутри конкретной секунды между документами, она не может эффективно применить стандартный индекс только по полю Период. Вместо быстрого поиска по индексу (Index Seek) СУБД часто переходит к тяжелым операциям сканирования таблиц (Table Scan/Index Scan), что кратно увеличивает нагрузку на процессор и дисковую подсистему сервера. На крупных базах данных такие запросы могут выполняться в 2.5–8 раз медленнее, чем аналогичные запросы по простой дате!

Магия формулы «КонецМесяца(Дата) + 1» и ее преимущества

Посмотрим на пример оптимизации, который позволяет получить абсолютно идентичный результат, но в разы быстрее.

Если нам требуется получить остатки на конец месяца (например, на конец марта — 31.03.2019 23:59:59 включая все движения), вместо построения тяжелой конструкции Новый Граница(КонецМесяца(Дата), ВидГраницы.Включая) мы можем просто прибавить одну секунду к этой дате:


// Высокооптимизированный способ получения остатков на конец месяца
ОптимальнаяДата = КонецМесяца(РабочаяДата) + 1; // Получаем 01.04.2019 00:00:00

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТоварыНаСкладахОстатки.Номенклатура,
    |   ТоварыНаСкладахОстатки.КоличествоОстаток
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";

Запрос.УстановитьПараметр("Период", ОптимальнаяДата);
Запрос.УстановитьПараметр("Номенклатура", ВыбранныйТовар);
РезультатЗапроса = Запрос.Выполнить();

Почему этот код работает быстрее?

Дата 01.04.2019 00:00:00 — это в точности первая секунда следующего месяца, то есть точка хранения рассчитанных итогов в физической таблице СУБД. При таком запросе платформе 1С не требуется обращаться к таблице движений и «откручивать» остатки назад или вперед. СУБД напрямую и мгновенно считывает готовые данные из таблицы итогов по простому индексу даты. Результат получается абсолютно идентичным использованию границы, но скорость выполнения запроса возрастает многократно.

Срез последних в периодических регистрах сведений

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

Если в течение одной и той же секунды были записаны два разных документа, изменяющих цену товара, то обычный запрос метода СрезПоследних по простой Дате вернет непредсказуемый результат (или значение, актуальное до проведения этих документов). Для гарантированного получения актуальной цены на момент проведения конкретного документа в параметры метода СрезПоследних необходимо передавать именно МоментВремени этого документа:


// Получение среза последних на момент времени документа
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ЦеныНоменклатурыСрезПоследних.Цена
    |ИЗ
    |   РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура = &Номенклатура) КАК ЦеныНоменклатурыСрезПоследних";

Запрос.УстановитьПараметр("Период", ЭтотОбъект.МоментВремени());
Запрос.УстановитьПараметр("Номенклатура", ВыбранныйТовар);
РезультатЗапроса = Запрос.Выполнить();

Современные стандарты разработки и отказ от реального времени

В современных типовых конфигурациях (1С:ERP, КА, УТ, УНФ) разработчики минимизируют использование оперативного контроля остатков в режиме реального времени на конкретный МоментВремени.

Выясним причину этого решения. При проведении документов в многопользовательской базе блокировки таблиц на МоментВремени часто приводили к взаимным блокировкам (deadlocks). СУБД тратила слишком много ресурсов на обеспечение транзакционной целостности «внутри» секунды.

В современных конфигурациях применяется интервальный контроль остатков. Проверка наличия товаров сместилась с точечного анализа («внутри секунды проведения документа») на анализ остатков на конец дня или в пределах границы периода. Это позволило полностью переписать запросы проведения документов с использования тяжелого МоментаВремени на простую и быструю Дату, значительно повысив общую масштабируемость и производительность всей системы.

Резюме: правила выбора параметров

Подведем краткий итог и сформируем правила для повседневной разработки:

  1. Для оперативной проверки остатков при проведении документа (исключая движения самого документа) используем ЭтотОбъект.МоментВремени().
  2. Если нужно получить остатки на конец дня со всеми движениями последней секунды, используем Новый Граница(КонецДня(Дата), ВидГраницы.Включая).
  3. Если нужно максимально быстро получить остатки на конец месяца, используем самую оптимальную конструкцию: КонецМесяца(Дата) + 1. Это обеспечивает прямое чтение из таблицы итогов СУБД без вычислений.
  4. В периодических регистрах сведений для получения точной записи на момент документа используем МоментВремени или Границу.
← На главную