Регистр сведений изнутри: хранение, срезы, запись и скорость запросов

· 9 мин

Содержание

Регистр сведений (РС, в некоторых источниках — РЛС) в 1С 8.3 — это таблица, где строка однозначно определяется набором измерений, а для периодического регистра — ещё и периодом. Работает он как справочная таблица «ключ → значения»: цены номенклатуры, курсы валют, ставки, настройки. Ключевые решения при его проектировании — периодичность, режим записи и порядок измерений: от них зависят и целостность данных, и скорость каждого запроса.

Тема общая для платформы: устройство регистра одинаково в БП 3.0, ЗУП 3.1, УТ 11 и самописных конфигурациях. Примеры кода рассчитаны на 8.3 в режиме управляемых блокировок.

Из чего состоит регистр сведений и как он хранится в базе

В конфигураторе у регистра сведений четыре группы полей:

  • 🔑 Измерения — ключ записи. Комбинация значений измерений (плюс период) уникальна для независимого регистра.
  • 📊 Ресурсы — хранимые значения: цена, курс, ставка. В отличие от регистра накопления, итогов по ресурсам нет — это просто данные.
  • 📝 Реквизиты — дополнительная информация к записи: комментарий, автор изменения, основание.
  • 📅 Стандартные реквизиты — Период (у периодических), Регистратор, НомерСтроки и Активность (у подчинённых регистратору).

Физическая таблица

Каждый регистр сведений — отдельная таблица СУБД с именем вида _InfoRgNNN, где NNN — внутренний номер. Измерения, ресурсы и реквизиты превращаются в колонки _FldNNN. Узнать, какая таблица за каким объектом стоит, можно методом ПолучитьСтруктуруХраненияБазыДанных():

Метаданные_ = Новый Массив;
Метаданные_.Добавить("РегистрСведений.ЦеныНоменклатуры");
Структура = ПолучитьСтруктуруХраненияБазыДанных(Метаданные_);
Для Каждого Строка Из Структура Цикл
    Сообщить(Строка.Метаданные + " → " + Строка.ИмяТаблицыХранения + " (" + Строка.Назначение + ")");
КонецЦикла;

Если у периодического регистра включены итоги среза последних или первых, платформа создаёт дополнительные таблицы _InfoRgSL и _InfoRgSF. Они хранят уже готовый срез и ускоряют чтение.

Порядок измерений имеет значение

Основной индекс таблицы строится по ключу записи, и измерения входят в него в том порядке, в каком стоят в дереве конфигуратора. Поэтому первым ставьте измерение, по которому чаще всего отбирают данные. Для регистра цен это обычно Номенклатура, а не ВидЦены, если отборы почти всегда идут по товару.

Менять порядок измерений в работающей базе можно, но при обновлении структуры платформа перестроит таблицу. На больших регистрах это долго — планируйте на нерабочее время.

Периодичность и срезы: как регистр сведений хранит историю

Периодичность задаётся свойством регистра. Варианты:

ПериодичностьЧто хранитПример
НепериодическийОдно текущее значение на ключНастройки пользователя, штрихкоды
В пределах секундыЗначение на конкретный моментСтатусы с точным временем
В пределах дняЗначение на датуКурсы валют, цены
В пределах месяца / квартала / годаЗначение на начало периодаПлановые нормы, лимиты
В пределах периода регистратораПериод = дата документа-регистратораТолько для подчинённых регистров

При периодичности «В пределах дня» две записи с одинаковыми измерениями на 10:00 и 15:00 одного дня невозможны: период приводится к началу дня, и вторая запись заменит первую.

Срез последних и срез первых

Для периодического регистра платформа даёт две виртуальные таблицы:

  • ⏩ СрезПоследних(Период, Условие) — для каждого ключа последняя запись на дату или раньше неё. Это «какая цена действовала на 15 марта».
  • ⏪ СрезПервых(Период, Условие) — первая запись на дату или позже. Пригодится для «когда цена изменится в следующий раз».

Типовой запрос цен на дату документа:

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

Если период не передан, срез берётся на текущий момент без ограничения сверху — то есть с учётом записей «будущими» датами. Для запроса «на сегодня» дату лучше передавать явно.

Для разовых чтений без запроса есть методы менеджера:

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

Для непериодического регистра используйте Получить(Отбор): он вернёт структуру ресурсов. Если записи нет, ресурсы будут пустыми (для числа — 0).

Режимы записи регистра сведений: независимый и подчинённый регистратору

Это главное архитектурное решение, и после наполнения базы его меняют неохотно.

СвойствоНезависимыйПодчинение регистратору
Кто пишетПользователь, код, обработкиТолько документ при проведении
Уникальность ключаКонтролируется платформойНе контролируется
Менеджер записиЕстьНет
Удаление записейВручную или кодомВместе с отменой проведения
Поле АктивностьНетЕсть

Независимый регистр

Записи живут сами по себе. Пользователь открывает список регистра и правит строку, код пишет через набор или менеджер записи. Платформа не даст записать две строки с одинаковым периодом и измерениями — сработает ошибка Запись с такими ключевыми полями существует!.

Подчинение регистратору

Каждая запись привязана к документу. Документ формирует движения в обработке проведения, при отмене проведения они удаляются. Дубли ключей здесь возможны: два документа на одну дату могут записать цену одному товару. Срез последних тогда вернёт одну из строк, и какую — заранее не скажешь. Такие ситуации закрывают проверкой при проведении.

Процедура ОбработкаПроведения(Отказ, Режим)
    Движения.ЦеныНоменклатуры.Записывать = Истина;
    Для Каждого СтрокаТЧ Из Товары Цикл
        Движение = Движения.ЦеныНоменклатуры.Добавить();
        Движение.Период = Дата;
        Движение.Номенклатура = СтрокаТЧ.Номенклатура;
        Движение.ВидЦены = ВидЦены;
        Движение.Цена = СтрокаТЧ.Цена;
    КонецЦикла;
КонецПроцедуры

Запись в регистр сведений из кода: наборы, менеджер, блокировки

Набор записей

Набор — основной инструмент. Он читает и пишет группу строк по отбору. Отбор работает как граница: при записи с замещением платформа удалит всё, что попадает под отбор, и запишет содержимое набора.

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

Запись = Набор.Добавить();
Запись.Период = НачалоДня(ТекущаяДатаСеанса());
Запись.Номенклатура = Товар;
Запись.ВидЦены = ВидЦены;
Запись.Цена = 1250;

Набор.Записать(Истина); // Истина = замещать

Набор без установленного отбора при записи с замещением очищает весь регистр. Это классическая авария: разработчик забыл строку Отбор.…Установить(), и цены по всей базе исчезли. Перед запуском такого кода на рабочей базе сделайте резервную копию.

Параметр Ложь — запись без замещения: строки добавляются, старые не трогаются. Для независимого регистра при совпадении ключа это вызовет ошибку уникальности.

Менеджер записи

Удобен, когда нужно прочитать, поменять или удалить ровно одну строку независимого регистра:

Менеджер = РегистрыСведений.НастройкиПользователей.СоздатьМенеджерЗаписи();
Менеджер.Пользователь = ТекущийПользователь;
Менеджер.Настройка = "ВидЦеныПоУмолчанию";
Менеджер.Прочитать();
Если Менеджер.Выбран() Тогда
    Менеджер.Значение = НовоеЗначение;
    Менеджер.Записать(Истина);
КонецЕсли;

Прочитать() при отсутствии записи не падает, но очищает ресурсы. Проверяйте Выбран() до изменения.

Управляемые блокировки

При записи по схеме «прочитать → посчитать → записать» два сеанса могут затереть изменения друг друга. Защита — исключительная блокировка по ключу до чтения:

НачатьТранзакцию();
Попытка
    Блокировка = Новый БлокировкаДанных;
    Элемент = Блокировка.Добавить("РегистрСведений.Счетчики");
    Элемент.Режим = РежимБлокировкиДанных.Исключительный;
    Элемент.УстановитьЗначение("Ключ", ИмяСчетчика);
    Блокировка.Заблокировать();

    Менеджер = РегистрыСведений.Счетчики.СоздатьМенеджерЗаписи();
    Менеджер.Ключ = ИмяСчетчика;
    Менеджер.Прочитать();
    Менеджер.Ключ = ИмяСчетчика; // на случай, если записи не было
    Менеджер.Значение = Менеджер.Значение + 1;
    Менеджер.Записать(Истина);

    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Блокируйте как можно уже: по конкретным значениям измерений, а не всю таблицу. Блокировка без УстановитьЗначение закрывает регистр целиком для всех пользователей.

Перед записью в регистр сведений из кода
Выполнено: 0 / 5

Ведущие измерения и связь регистра с другими объектами

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

Свойство «Ведущее»

У измерения ссылочного типа есть флажок Ведущее. Он даёт два эффекта:

  1. В форме объекта (например, карточки номенклатуры) в панели навигации появляется ссылка на записи регистра по этому объекту.
  2. При удалении объекта записи регистра, где он указан в ведущем измерении, удаляются вместе с ним.

Для регистра штрихкодов, где измерение Номенклатура ведущее, удаление товара не оставит «висящих» штрихкодов.

Связь с документом-регистратором

У подчинённого регистра роль «родителя» играет документ. Записи видны в отчёте о движениях документа, а у регистра поле Регистратор составного типа — если регистраторов несколько. При чтении такого поля в запросе учитывайте, что платформа соединяет таблицы всех документов-регистраторов. Обращение Цены.Регистратор.Номер по составному типу даёт тяжёлый запрос — используйте ВЫРАЗИТЬ(Цены.Регистратор КАК Документ.УстановкаЦен).Номер.

Регистры как подчинённые справочники

Непериодический независимый регистр с ведущим измерением — частая замена табличной части. Плюс: записи можно менять, не перезаписывая весь объект и не ловя его блокировку. Минус: нет единой транзакции записи объекта и его «строк», это нужно обеспечить кодом.

Оптимизация запросов к регистру сведений и индексы

Условия — в параметры виртуальной таблицы

Самая частая ошибка — фильтр в ГДЕ после среза:

// Медленно: срез строится по всем товарам, отбор — потом
|ИЗ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата) КАК Цены
|ГДЕ Цены.Номенклатура В (&СписокТоваров)

// Быстро: отбор попадает внутрь подзапроса среза
|ИЗ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата,
|    Номенклатура В (&СписокТоваров)) КАК Цены

На регистре в миллионы строк разница — от секунд до минут.

Срез по датам документов — не соединением

Нужна цена на дату каждой строки, а даты разные? Соединение с виртуальной таблицей по дате не сработает: у неё один параметр Период. Правильный приём — найти максимальный период через соединение с основной таблицей и затем взять ресурсы:

|ВЫБРАТЬ
|    Док.Ссылка КАК Документ,
|    Док.Номенклатура КАК Номенклатура,
|    МАКСИМУМ(Цены.Период) КАК Период
|ПОМЕСТИТЬ ВТ_Периоды
|ИЗ
|    ВТ_Документы КАК Док
|        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
|        ПО Док.Номенклатура = Цены.Номенклатура
|            И Цены.Период <= Док.Дата
|СГРУППИРОВАТЬ ПО
|    Док.Ссылка,
|    Док.Номенклатура

Вторым пакетом соединяете ВТ_Периоды с регистром по номенклатуре и периоду и забираете цену.

Индексы

У измерений, ресурсов и реквизитов есть свойство Индексировать:

  • ✅ Не индексировать — по умолчанию для ресурсов и реквизитов.
  • ✅ Индексировать — отдельный индекс по полю.
  • ✅ Индексировать с доп. упорядочиванием — индекс плюс сортировка, полезно для динамических списков.

Индекс нужен по полю, по которому часто ищут без первого измерения. Например, поиск товара по штрихкоду, если первое измерение — номенклатура. Лишние индексы замедляют каждую запись, поэтому ставьте их по реальным запросам, а не «на всякий случай».

Итоги среза последних

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

Итоги ускоряют срез на текущий момент. Срез на дату в прошлом по-прежнему строится по основной таблице.

Где искать медленные места

  • 🔍 Технологический журнал и замер производительности покажут конкретный запрос.
  • 🔍 Для MS SQL — план запроса: сканирование всей таблицы _InfoRgNNN говорит, что условие не попало в индекс.
  • 🔍 Проверьте, не стоит ли нужное измерение последним в списке: тогда основной индекс отбору почти не помогает.

Частые вопросы о регистрах сведений

Чем регистр сведений отличается от регистра накопления?
Регистр сведений хранит значения по ключу, без итогов и без вида движения. Регистр накопления хранит приход и расход и считает остатки и обороты.
Можно ли сменить периодичность у заполненного регистра?
Можно, но при обновлении структуры периоды будут приведены к новой периодичности, и записи с одинаковым новым ключом конфликтуют. Сначала проверьте данные на копии базы.
Почему срез последних возвращает не ту цену?
Чаще всего в регистре есть запись с более поздней датой, чем ожидалось, или в подчинённом регистре два документа записали цену на одну дату. Проверьте записи списком регистра с отбором по товару.
Как очистить регистр сведений полностью?
Создайте набор записей без отбора и запишите его пустым с замещением: Набор.Записать(Истина). Делайте это только в копии базы или после резервного копирования.
Нужен ли регистр сведений, если можно добавить реквизит в справочник?
Если значение одно и без истории — хватит реквизита. Если нужна история по датам или ключ из нескольких полей — нужен регистр.