Почему возникает ошибка «Индекс находится за границами массива» при работе с набором записей в 1С и как её исправить?

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

При разработке в среде 1С:Предприятие одной из самых частых и одновременно досадных ошибок становится предупреждение «Индекс находится за границами массива». Особенно часто с ней сталкиваются начинающие программисты при попытке считать или изменить данные в регистрах сведений с помощью набора записей. На первый взгляд код выглядит логичным, данные в базе точно присутствуют, но система упорно отказывается выполнять алгоритм и выбрасывает исключение.

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

Анатомия ошибки: почему количество записей равно нулю?

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


наборЗаписей = РегистрыСведений.Контакты.СоздатьНаборЗаписей();
ПоследняяЗапись = НаборЗаписей[наборЗаписей.Количество() - 1];

При выполнении этой нехитрой операции платформа мгновенно выдает ошибку выхода за границы массива. Автор недоумевает: «Ведь записи в регистре точно есть! Я вижу их в режиме 1С:Предприятие!». Давайте проанализируем, что происходит на уровне платформы пошагово.

Метод СоздатьНаборЗаписей() — это конструктор, который лишь инициализирует новый пустой объект-контейнер типа РегистрСведенийНаборЗаписей в оперативной памяти компьютера (на стороне сервера приложений). В этот момент никакого обращения к базе данных (СУБД) не происходит. Платформа просто выделила память под структуру будущего набора, но сам набор пуст.

Следовательно, выражение наборЗаписей.Количество() сразу после создания набора возвращает число 0. Теперь подставим это значение в формулу получения индекса:

Индекс = Количество() - 1 => 0 - 1 = -1

Конструкция превращается в наборЗаписей[-1]. Так как в коллекциях 1С (как и в абсолютном большинстве языков программирования) индексация элементов начинается с нуля, элемента с индексом -1 физически не существует. Платформа реагирует на это единственно возможным способом — прерывает транзакцию и сообщает, что индекс находится за границами массива.

Ключевое звено: загрузка данных с помощью метода Прочитать()

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

Однако просто вызвать метод Прочитать() сразу после создания набора — это грубейшая ошибка, которая в реальной рабочей базе может привести к катастрофическим последствиям. Давайте разберем, почему.

Опасность чтения без установки отборов

Если мы выполним метод Прочитать() для набора записей без предварительно наложенных фильтров, платформа попытается прочесть в оперативную память **вообще все** записи, которые содержатся в данном регистре сведений. Если в вашей базе данных регистр Контакты содержит миллионы строк, систему ждут серьезные проблемы:

  1. Перерасход оперативной памяти (Out of Memory): Сервер приложений попытается аллоцировать гигабайты ОЗУ под размещение огромного массива объектов. В лучшем случае сервер начнет жутко «тормозить», в худшем — рабочий процесс rphost аварийно завершится, выбросив всех пользователей из системы.
  2. Блокировки таблиц СУБД: При чтении колоссального объема данных СУБД (MS SQL Server, PostgreSQL) наложит разделяемые блокировки (Shared Locks) на всю таблицу регистра. В это время ни один пользователь не сможет записать новые контакты или изменить старые. Система фактически «встанет».
  3. Длительное ожидание: Запрос будет обрабатываться крайне медленно, что приведет к превышению времени ожидания блокировки другими транзакциями (Lock Timeout).

Именно поэтому перед вызовом Прочитать() мы **всегда** должны жестко ограничивать выборку с помощью свойства Отбор. Мы обязаны указать конкретные значения измерений (например, конкретное физическое лицо, организацию или период), для которых мы хотим получить данные.

Подводные камни метода Записать()

Разработчикам важно помнить о коварном поведении метода Записать(). По умолчанию этот метод работает в режиме замещения данных (параметр Замещать по умолчанию равен Истина). Это рождает две критические опасности:

  1. Случайное удаление данных: Представьте, что вы создали набор записей, установили отбор по конкретному контрагенту, но забыли вызвать метод Прочитать(). Набор остался пустым. Если после этого вы вызовете метод Записать(), платформа воспримет это как команду: «Очистить все записи по этому контрагенту в базе данных и записать вместо них то, что сейчас лежит в наборе (то есть ничего)». Произойдет безвозвратное удаление строк!
  2. Полная очистка регистра: Если вы по ошибке выполните метод Записать() у нового набора записей, у которого вообще не задан отбор и не вызван метод Прочитать(), платформа без единого предупреждения полностью очистит всю таблицу этого регистра сведений в базе данных. Восстановить данные в таком случае удастся только из резервной копии.

Что имел в виду автор под фразой «дело в дублях»?

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

Ключевыми полями для регистра сведений являются его измерения (а для периодических регистров — еще и дата/время периода). Если вы попытаетесь записать в регистр строку, которая полностью совпадает по измерениям с уже существующей строкой, то:

Если в вашем коде подготовка данных идет динамически (например, вы переносите информацию из внешнего файла Excel), перед заполнением набора записей настоятельно рекомендуется предварительно свернуть исходную таблицу. Давайте посмотрим, как это элегантно сделать с помощью объекта ТаблицаЗначений:


// Создаем временную таблицу для подготовки уникальных данных
ТЗДанных = Новый ТаблицаЗначений;
ТЗДанных.Колонки.Добавить("ФизЛицо");
ТЗДанных.Колонки.Добавить("ВидСвязи");
ТЗДанных.Колонки.Добавить("Представление");

// Представим, что мы заполнили ТЗДанных из внешнего источника, и там могут быть дубли.
// Сворачиваем таблицу по ключевым измерениям, суммируя или усредняя ресурсы (если нужно)
ТЗДанных.Свернуть("ФизЛицо, ВидСвязи", "Представление");

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

Разработаем идеальный шаблон безопасного кода

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


// Шаг 1. Инициализируем пустой набор записей в памяти
НаборЗаписей = РегистрыСведений.Контакты.СоздатьНаборЗаписей();

// Шаг 2. Обязательно устанавливаем отбор! Ограничиваем выборку конкретным физлицом
НаборЗаписей.Отбор.ФизЛицо.Установить(ВыбранноеФизЛицо);

// Шаг 3. Заполняем наш набор данными из базы данных на основе отбора
НаборЗаписей.Прочитать();

// Шаг 4. Делаем обязательную проверку: есть ли вообще записи в прочитанном наборе?
Если НаборЗаписей.Количество() > 0 Тогда

    // Шаг 5. Теперь мы в полной безопасности можем обратиться к последнему элементу по индексу
    ИндексПоследнейЗаписи = НаборЗаписей.Количество() - 1;
    ПоследняяЗапись = НаборЗаписей[ИндексПоследнейЗаписи];
    
    // Выведем информацию для демонстрации совместной работы
    Сообщить("Успешно прочитана последняя запись: " + ПоследняяЗапись.Представление);
    
    // При необходимости модифицируем данные
    ПоследняяЗапись.Представление = "+7 (999) 000-00-00";
    
    // Шаг 6. Записываем изменения обратно в базу данных
    НаборЗаписей.Записать();
    
Иначе
    // Обработка ситуации, когда по данному отбору в базе еще нет ни одной записи
    Сообщить("Для указанного физического лица записи в регистре отсутствуют. Создаем новую запись...");
    
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.ФизЛицо = ВыбранноеФизЛицо;
    НоваяЗапись.ВидСвязи = Перечисления.ВидыСвязи.Телефон;
    НоваяЗапись.Представление = "+7 (999) 000-00-00";
    
    НаборЗаписей.Записать();
КонецЕсли;

Подведем итоги совместной работы

Мы подробно проанализировали причины возникновения ошибки выхода за границы массива при работе с регистрами сведений. Главный вывод: никогда не пытайтесь обращаться к элементам набора записей сразу после его создания методом СоздатьНаборЗаписей(). Всегда помните о трех золотых правилах:

  1. Сначала устанавливайте точечные, максимально ограничивающие отборы.
  2. Только после установки отборов выполняйте метод Прочитать().
  3. Перед обращением к элементам по индексу всегда делайте логическую проверку Если НаборЗаписей.Количество() > 0 Тогда.

Соблюдение этих простых стандартов разработки 1С убережет ваши приложения от ошибок времени выполнения, сохранит целостность ваших баз данных и обеспечит высокую скорость работы системы даже при колоссальных нагрузках.

← На главную