Регистр сведений изнутри: хранение, срезы, запись и скорость запросов
Содержание
Регистр сведений (РС, в некоторых источниках — РЛС) в 1С 8.3 — это таблица, где строка однозначно определяется набором измерений, а для периодического регистра — ещё и периодом. Работает он как справочная таблица «ключ → значения»: цены номенклатуры, курсы валют, ставки, настройки. Ключевые решения при его проектировании — периодичность, режим записи и порядок измерений: от них зависят и целостность данных, и скорость каждого запроса.
Тема общая для платформы: устройство регистра одинаково в БП 3.0, ЗУП 3.1, УТ 11 и самописных конфигурациях. Примеры кода рассчитаны на 8.3 в режиме управляемых блокировок.
Из чего состоит регистр сведений и как он хранится в базе
В конфигураторе у регистра сведений четыре группы полей:
- 🔑 Измерения — ключ записи. Комбинация значений измерений (плюс период) уникальна для независимого регистра.
- 📊 Ресурсы — хранимые значения: цена, курс, ставка. В отличие от регистра накопления, итогов по ресурсам нет — это просто данные.
- 📝 Реквизиты — дополнительная информация к записи: комментарий, автор изменения, основание.
- 📅 Стандартные реквизиты —
Период(у периодических),Регистратор,НомерСтрокииАктивность(у подчинённых регистратору).
Физическая таблица
Каждый регистр сведений — отдельная таблица СУБД с именем вида _InfoRgNNN, где NNN — внутренний номер. Измерения, ресурсы и реквизиты превращаются в колонки _FldNNN. Узнать, какая таблица за каким объектом стоит, можно методом ПолучитьСтруктуруХраненияБазыДанных():
Метаданные_ = Новый Массив;
Метаданные_.Добавить("РегистрСведений.ЦеныНоменклатуры");
Структура = ПолучитьСтруктуруХраненияБазыДанных(Метаданные_);
Для Каждого Строка Из Структура Цикл
Сообщить(Строка.Метаданные + " → " + Строка.ИмяТаблицыХранения + " (" + Строка.Назначение + ")");
КонецЦикла;
Если у периодического регистра включены итоги среза последних или первых, платформа создаёт дополнительные таблицы _InfoRgSL и _InfoRgSF. Они хранят уже готовый срез и ускоряют чтение.
Порядок измерений имеет значение
Основной индекс таблицы строится по ключу записи, и измерения входят в него в том порядке, в каком стоят в дереве конфигуратора. Поэтому первым ставьте измерение, по которому чаще всего отбирают данные. Для регистра цен это обычно Номенклатура, а не ВидЦены, если отборы почти всегда идут по товару.
Менять порядок измерений в работающей базе можно, но при обновлении структуры платформа перестроит таблицу. На больших регистрах это долго — планируйте на нерабочее время.
Периодичность и срезы: как регистр сведений хранит историю
Периодичность задаётся свойством регистра. Варианты:
| Периодичность | Что хранит | Пример |
|---|---|---|
| Непериодический | Одно текущее значение на ключ | Настройки пользователя, штрихкоды |
| В пределах секунды | Значение на конкретный момент | Статусы с точным временем |
| В пределах дня | Значение на дату | Курсы валют, цены |
| В пределах месяца / квартала / года | Значение на начало периода | Плановые нормы, лимиты |
| В пределах периода регистратора | Период = дата документа-регистратора | Только для подчинённых регистров |
При периодичности «В пределах дня» две записи с одинаковыми измерениями на 10:00 и 15:00 одного дня невозможны: период приводится к началу дня, и вторая запись заменит первую.
Срез последних и срез первых
Для периодического регистра платформа даёт две виртуальные таблицы:
- ⏩ СрезПоследних(Период, Условие) — для каждого ключа последняя запись на дату или раньше неё. Это «какая цена действовала на 15 марта».
- ⏪ СрезПервых(Период, Условие) — первая запись на дату или позже. Пригодится для «когда цена изменится в следующий раз».
Типовой запрос цен на дату документа:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
| &Дата,
| ВидЦены = &ВидЦены
| И Номенклатура В (&СписокТоваров)) КАК Цены";
Запрос.УстановитьПараметр("Дата", ДатаДокумента);
Запрос.УстановитьПараметр("ВидЦены", ВидЦены);
Запрос.УстановитьПараметр("СписокТоваров", МассивТоваров);
Выборка = Запрос.Выполнить().Выбрать();
Если период не передан, срез берётся на текущий момент без ограничения сверху — то есть с учётом записей «будущими» датами. Для запроса «на сегодня» дату лучше передавать явно.
Для разовых чтений без запроса есть методы менеджера:
Отбор = Новый Структура("Номенклатура, ВидЦены", Товар, ВидЦены);
Значения = РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(ДатаДокумента, Отбор);
ЦенаНаДату = Значения.Цена;
Для непериодического регистра используйте Получить(Отбор): он вернёт структуру ресурсов. Если записи нет, ресурсы будут пустыми (для числа — 0).
Режимы записи регистра сведений: независимый и подчинённый регистратору
Это главное архитектурное решение, и после наполнения базы его меняют неохотно.
| Свойство | Независимый | Подчинение регистратору |
|---|---|---|
| Кто пишет | Пользователь, код, обработки | Только документ при проведении |
| Уникальность ключа | Контролируется платформой | Не контролируется |
| Менеджер записи | Есть | Нет |
| Удаление записей | Вручную или кодом | Вместе с отменой проведения |
Поле Активность | Нет | Есть |
Независимый регистр
Записи живут сами по себе. Пользователь открывает список регистра и правит строку, код пишет через набор или менеджер записи. Платформа не даст записать две строки с одинаковым периодом и измерениями — сработает ошибка Запись с такими ключевыми полями существует!.
Подчинение регистратору
Каждая запись привязана к документу. Документ формирует движения в обработке проведения, при отмене проведения они удаляются. Дубли ключей здесь возможны: два документа на одну дату могут записать цену одному товару. Срез последних тогда вернёт одну из строк, и какую — заранее не скажешь. Такие ситуации закрывают проверкой при проведении.
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ЦеныНоменклатуры.Записывать = Истина;
Для Каждого СтрокаТЧ Из Товары Цикл
Движение = Движения.ЦеныНоменклатуры.Добавить();
Движение.Период = Дата;
Движение.Номенклатура = СтрокаТЧ.Номенклатура;
Движение.ВидЦены = ВидЦены;
Движение.Цена = СтрокаТЧ.Цена;
КонецЦикла;
КонецПроцедуры
Запись в регистр сведений из кода: наборы, менеджер, блокировки
Набор записей
Набор — основной инструмент. Он читает и пишет группу строк по отбору. Отбор работает как граница: при записи с замещением платформа удалит всё, что попадает под отбор, и запишет содержимое набора.
Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
Набор.Отбор.Период.Установить(НачалоДня(ТекущаяДатаСеанса()));
Набор.Отбор.Номенклатура.Установить(Товар);
Набор.Отбор.ВидЦены.Установить(ВидЦены);
Запись = Набор.Добавить();
Запись.Период = НачалоДня(ТекущаяДатаСеанса());
Запись.Номенклатура = Товар;
Запись.ВидЦены = ВидЦены;
Запись.Цена = 1250;
Набор.Записать(Истина); // Истина = замещать
Набор без установленного отбора при записи с замещением очищает весь регистр. Это классическая авария: разработчик забыл строку Отбор.…Установить(), и цены по всей базе исчезли. Перед запуском такого кода на рабочей базе сделайте резервную копию.
Параметр Ложь — запись без замещения: строки добавляются, старые не трогаются. Для независимого регистра при совпадении ключа это вызовет ошибку уникальности.
Менеджер записи
Удобен, когда нужно прочитать, поменять или удалить ровно одну строку независимого регистра:
Менеджер = РегистрыСведений.НастройкиПользователей.СоздатьМенеджерЗаписи();
Менеджер.Пользователь = ТекущийПользователь;
Менеджер.Настройка = "ВидЦеныПоУмолчанию";
Менеджер.Прочитать();
Если Менеджер.Выбран() Тогда
Менеджер.Значение = НовоеЗначение;
Менеджер.Записать(Истина);
КонецЕсли;
Прочитать() при отсутствии записи не падает, но очищает ресурсы. Проверяйте Выбран() до изменения.
Управляемые блокировки
При записи по схеме «прочитать → посчитать → записать» два сеанса могут затереть изменения друг друга. Защита — исключительная блокировка по ключу до чтения:
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрСведений.Счетчики");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
Элемент.УстановитьЗначение("Ключ", ИмяСчетчика);
Блокировка.Заблокировать();
Менеджер = РегистрыСведений.Счетчики.СоздатьМенеджерЗаписи();
Менеджер.Ключ = ИмяСчетчика;
Менеджер.Прочитать();
Менеджер.Ключ = ИмяСчетчика; // на случай, если записи не было
Менеджер.Значение = Менеджер.Значение + 1;
Менеджер.Записать(Истина);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Блокируйте как можно уже: по конкретным значениям измерений, а не всю таблицу. Блокировка без УстановитьЗначение закрывает регистр целиком для всех пользователей.
Ведущие измерения и связь регистра с другими объектами
Регистр сведений можно связать с объектами конфигурации так, чтобы получилась иерархия «объект → его записи».
Свойство «Ведущее»
У измерения ссылочного типа есть флажок Ведущее. Он даёт два эффекта:
- В форме объекта (например, карточки номенклатуры) в панели навигации появляется ссылка на записи регистра по этому объекту.
- При удалении объекта записи регистра, где он указан в ведущем измерении, удаляются вместе с ним.
Для регистра штрихкодов, где измерение Номенклатура ведущее, удаление товара не оставит «висящих» штрихкодов.
Связь с документом-регистратором
У подчинённого регистра роль «родителя» играет документ. Записи видны в отчёте о движениях документа, а у регистра поле Регистратор составного типа — если регистраторов несколько. При чтении такого поля в запросе учитывайте, что платформа соединяет таблицы всех документов-регистраторов. Обращение Цены.Регистратор.Номер по составному типу даёт тяжёлый запрос — используйте ВЫРАЗИТЬ(Цены.Регистратор КАК Документ.УстановкаЦен).Номер.
Регистры как подчинённые справочники
Непериодический независимый регистр с ведущим измерением — частая замена табличной части. Плюс: записи можно менять, не перезаписывая весь объект и не ловя его блокировку. Минус: нет единой транзакции записи объекта и его «строк», это нужно обеспечить кодом.
Оптимизация запросов к регистру сведений и индексы
Условия — в параметры виртуальной таблицы
Самая частая ошибка — фильтр в ГДЕ после среза:
// Медленно: срез строится по всем товарам, отбор — потом
|ИЗ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата) КАК Цены
|ГДЕ Цены.Номенклатура В (&СписокТоваров)
// Быстро: отбор попадает внутрь подзапроса среза
|ИЗ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата,
| Номенклатура В (&СписокТоваров)) КАК Цены
На регистре в миллионы строк разница — от секунд до минут.
Срез по датам документов — не соединением
Нужна цена на дату каждой строки, а даты разные? Соединение с виртуальной таблицей по дате не сработает: у неё один параметр Период. Правильный приём — найти максимальный период через соединение с основной таблицей и затем взять ресурсы:
|ВЫБРАТЬ
| Док.Ссылка КАК Документ,
| Док.Номенклатура КАК Номенклатура,
| МАКСИМУМ(Цены.Период) КАК Период
|ПОМЕСТИТЬ ВТ_Периоды
|ИЗ
| ВТ_Документы КАК Док
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
| ПО Док.Номенклатура = Цены.Номенклатура
| И Цены.Период <= Док.Дата
|СГРУППИРОВАТЬ ПО
| Док.Ссылка,
| Док.Номенклатура
Вторым пакетом соединяете ВТ_Периоды с регистром по номенклатуре и периоду и забираете цену.
Индексы
У измерений, ресурсов и реквизитов есть свойство Индексировать:
- ✅ Не индексировать — по умолчанию для ресурсов и реквизитов.
- ✅ Индексировать — отдельный индекс по полю.
- ✅ Индексировать с доп. упорядочиванием — индекс плюс сортировка, полезно для динамических списков.
Индекс нужен по полю, по которому часто ищут без первого измерения. Например, поиск товара по штрихкоду, если первое измерение — номенклатура. Лишние индексы замедляют каждую запись, поэтому ставьте их по реальным запросам, а не «на всякий случай».
Итоги среза последних
Для периодического регистра, где срез последних читают постоянно (цены в рознице, курсы), включите свойство «Разрешить итоги: срез последних». Платформа будет поддерживать таблицу _InfoRgSL в актуальном состоянии, и срез на текущую дату читается из неё без поиска по истории. Цена — чуть более медленная запись.
Итоги ускоряют срез на текущий момент. Срез на дату в прошлом по-прежнему строится по основной таблице.
Где искать медленные места
- 🔍 Технологический журнал и замер производительности покажут конкретный запрос.
- 🔍 Для MS SQL — план запроса: сканирование всей таблицы
_InfoRgNNNговорит, что условие не попало в индекс. - 🔍 Проверьте, не стоит ли нужное измерение последним в списке: тогда основной индекс отбору почти не помогает.
Частые вопросы о регистрах сведений
Чем регистр сведений отличается от регистра накопления?
Можно ли сменить периодичность у заполненного регистра?
Почему срез последних возвращает не ту цену?
Как очистить регистр сведений полностью?
Набор.Записать(Истина). Делайте это только в копии базы или после резервного копирования.