Мазмұнға өту
Tez Base

Тестовая база 1С отстала от рабочей: как сверить и как догрузить период

· Жарияланды: · Жаңартылды:

Бэкап и восстановление

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

Почему тест не обновляют - понятно, как только замеришь. На базе в 17,4 ГБ (MS SQL 2022, локальный диск) бэкап со сжатием занял 109 секунд, восстановление 73, вместе 182. Это 10,5 секунды на гигабайт. Если скорость сохранится, а на живом сервере она будет хуже, потому что диски делят с продом, то 10 ТБ - это около 30 часов. Плюс диск такого же размера под копию. И главное: полное восстановление стирает всё, что в тесте накручено - настроенные обмены, тестовых контрагентов, отключённые регламентные, полуготовую доработку, права под проверку.

При этом свежих данных за месяц в базе с историей за годы - единицы процентов строк. Возить ради них полную копию и убивать настройки - странная сделка.

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

Чем вы платите за этот подход

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

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

Шаг 1. Понять, чем тест отличается от рабочей базы

Вопросов на самом деле два, и путать их дорого.

Структура. Один и тот же ли состав таблиц и колонок, одинаковые ли типы. Если тест обновляли отдельно от прода, ответ уже “нет”, и сравнивать данные бессмысленно: разница будет следствием разной конфигурации.

Данные за период. Тут ответ почти всегда “нет”, и вся ценность в подробностях: какие таблицы разошлись и насколько.

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

-- Сверка структуры двух баз одной конфигурации.
-- Выполнять на сервере ТЕСТОВОЙ базы, ключ -b у sqlcmd обязателен.
SET NOCOUNT ON;

IF OBJECT_ID('tempdb..#s') IS NOT NULL DROP TABLE #s;
IF OBJECT_ID('tempdb..#d') IS NOT NULL DROP TABLE #d;
CREATE TABLE #s (tbl sysname, col sysname, typ sysname, len int, prec tinyint, scl tinyint);
CREATE TABLE #d (tbl sysname, col sysname, typ sysname, len int, prec tinyint, scl tinyint);

-- эталон: рабочая база
INSERT INTO #s
SELECT t.name, c.name, ty.name, c.max_length, c.precision, c.scale
FROM [YourProd].sys.tables t
JOIN [YourProd].sys.columns c ON c.object_id = t.object_id
JOIN [YourProd].sys.types ty ON ty.user_type_id = c.user_type_id;

-- приёмник: тестовая база
INSERT INTO #d
SELECT t.name, c.name, ty.name, c.max_length, c.precision, c.scale
FROM [YourTest].sys.tables t
JOIN [YourTest].sys.columns c ON c.object_id = t.object_id
JOIN [YourTest].sys.types ty ON ty.user_type_id = c.user_type_id;

CREATE CLUSTERED INDEX ix_s ON #s (tbl, col);
CREATE CLUSTERED INDEX ix_d ON #d (tbl, col);

DECLARE @net_tablic int, @net_kolonok int, @raznyy_tip int;

SELECT @net_tablic = COUNT(DISTINCT s.tbl)
FROM #s s WHERE NOT EXISTS (SELECT 1 FROM #d d WHERE d.tbl = s.tbl);

SELECT @net_kolonok = COUNT(*)
FROM #s s
WHERE EXISTS (SELECT 1 FROM #d d WHERE d.tbl = s.tbl)
  AND NOT EXISTS (SELECT 1 FROM #d d WHERE d.tbl = s.tbl AND d.col = s.col);

-- Самое опасное: имя совпадает, тип другой.
SELECT @raznyy_tip = COUNT(*)
FROM #s s JOIN #d d ON d.tbl = s.tbl AND d.col = s.col
WHERE s.typ <> d.typ OR s.len <> d.len OR s.prec <> d.prec OR s.scl <> d.scl;

SELECT kritichno_net_tablic = @net_tablic,
       kritichno_net_kolonok = @net_kolonok,
       kritichno_raznyy_tip = @raznyy_tip;

IF @net_tablic > 0 OR @net_kolonok > 0 OR @raznyy_tip > 0
BEGIN
    RAISERROR(N'STOP: структуры рабочей и тестовой базы расходятся.', 16, 1);
    RETURN;
END
PRINT N'Структуры совпадают, данные сравнимы.';

Третий счётчик и есть самый интересный. Проверку “все ли колонки на месте” проходят обе базы, а прогон на заведомо чужой паре (УТ против Бухгалтерии) дал 546 колонок с совпадающим именем и разным типом:

_Document549._Number   nvarchar(22) -> nchar(22)      разный тип
_Document552._Number   nvarchar(22) -> nvarchar(18)   разная длина

Для переноса это молча обрезанные значения: номер документа из nvarchar(22) в nvarchar(18) уедет короче, и никто не заметит.

Сверка самих данных

Самый ходовой способ сравнить две таблицы - посчитать строки. Он же самый бесполезный: строк поровну ровно в том случае, когда один документ удалили и один добавили, а это и есть интересный случай. При переносе между СУБД счётчик пропускает ещё и порчу значений внутри строк: при переезде с PostgreSQL на MS SQL даты молча слипаются, а строк остаётся столько же.

Для данных за период работает двусторонний EXCEPT: что есть в тесте и нет в эталоне, плюс наоборот. Сумма двух чисел и есть расхождение по таблице.

-- Расхождение по одной таблице за период, в обе стороны.
SELECT rashozhdeniy =
  (SELECT COUNT_BIG(*) FROM (
     SELECT _IDRRef, _Date_Time, _NumberPrefix, _Number FROM [YourTest].dbo._Document741 WHERE _Date_Time >= '4026-08-01'
     EXCEPT
     SELECT _IDRRef, _Date_Time, _NumberPrefix, _Number FROM [YourProd].dbo._Document741 WHERE _Date_Time >= '4026-08-01'
   ) x)
+ (SELECT COUNT_BIG(*) FROM (
     SELECT _IDRRef, _Date_Time, _NumberPrefix, _Number FROM [YourProd].dbo._Document741 WHERE _Date_Time >= '4026-08-01'
     EXCEPT
     SELECT _IDRRef, _Date_Time, _NumberPrefix, _Number FROM [YourTest].dbo._Document741 WHERE _Date_Time >= '4026-08-01'
   ) y);

Две вещи, без которых сверка врёт. Первая: колонка _Version выбрасывается всегда - это rowversion, он у каждой базы свой по построению, и его сравнение красит в красный вообще всё. Вторая: даты в таблицах 1С хранятся со сдвигом на 2000 лет, 4026-08-01 это 1 августа 2026 года. Отбор по периоду без сдвига молча не выберет ничего, и сверка радостно покажет ноль расхождений.

Для таблиц, которые сравниваются целиком, построчный EXCEPT уже неподъёмен. Там работает пара “число строк плюс контрольная сумма”: COUNT_BIG и CHECKSUM_AGG(BINARY_CHECKSUM(...)). Это ответ “совпало или нет” без разбора, что именно, но для справочника на миллион строк такой ответ и нужен.

Контрольный прогон сверки на паре баз одной конфигурации: 911 таблиц за 2,7 секунды, два расхождения - служебные константы “Настройки новостей” и “Состояние отправки серверных оповещений”, которые у теста всегда свои.

Шаг 2. Догрузить только свежий период

Наивная догрузка “вставь всё, что новее даты X” ломается о два факта: в проде документы задним числом перепроводят, а в тесте кто-то мог провести своё. Поэтому базовая операция другая: удалить период и вставить его заново. Повторный запуск даёт тот же результат, мусор не копится, удалённое в проде исчезает из теста.

Честная цена такой идемпотентности: документы, которые тест успел провести внутри догружаемого периода, тоже сотрутся. Сохраняется всё, что вне периода и вне переносимых таблиц - настройки, обмены, права.

-- Догрузка периода для документа с табличной частью. Только в ТЕСТОВУЮ базу.
DECLARE @granica datetime2(0) = '4026-08-01';
BEGIN TRAN;

-- Строки ТЧ удаляются по шапкам ОБЕИХ баз: если документ в тесте уже удалён,
-- его строки осиротели, по шапке приёмника не находятся, и INSERT упадёт на дубле ключа.
DELETE tc FROM [YourTest].dbo._Document741_VT22895 tc WITH (TABLOCK)
WHERE EXISTS (SELECT 1 FROM [YourTest].dbo._Document741 d
              WHERE d._IDRRef = tc._Document741_IDRRef AND d._Date_Time >= @granica)
   OR EXISTS (SELECT 1 FROM [YourProd].dbo._Document741 d
              WHERE d._IDRRef = tc._Document741_IDRRef AND d._Date_Time >= @granica);

DELETE FROM [YourTest].dbo._Document741 WITH (TABLOCK) WHERE _Date_Time >= @granica;

INSERT INTO [YourTest].dbo._Document741 WITH (TABLOCK)
      (_IDRRef, _Version, _Marked, _Date_Time, _NumberPrefix, _Number, _Posted)
SELECT _IDRRef, _Version, _Marked, _Date_Time, _NumberPrefix, _Number, _Posted
FROM [YourProd].dbo._Document741 WHERE _Date_Time >= @granica;

COMMIT;

Колонки перечисляются явно, а не через *: тогда лишние колонки доработанного теста переносу не мешают.

Таблицы итогов: верные цифры ещё не значат верную базу

Движения перенести мало. У регистров накопления есть таблицы итогов, и платформа им верит.

Пересчитать остатки из движений на SQL несложно, формула сходится с платформой до единицы: сумма по регистру, посчитанная тремя независимыми путями - запросом платформы, SQL по движениям и арифметикой по видам движения - дала одни и те же 87 081 на границу периода и 81 838 на конец.

Цифры сошлись сразу, а сюрприз сидел в составе строк. Платформа хранит срезы по своему правилу: начала месяцев от первого месяца движений плюс один до последнего включительно, плюс служебная строка “конца времён” с периодом 5999-11-01, в которой лежат текущие остатки. Без этой строки остатки в базе пустые. Мой первый пересчёт дал верные остатки и один лишний срез - и это не видно ни в одном отчёте: остатки сходятся, приёмку на глаз такая база проходит.

Поймало ошибку только побитовое сравнение таблиц через EXCEPT. С тех пор сверка после переноса у меня не опция, а часть скрипта.

Отдельная мелочь про те же итоги: у регистра “Товары к отгрузке” живой УТ все 5 450 строк таблицы итогов оказались с нулевыми ресурсами. Это регистр, где приходящее тут же расходуется: платформа при инкрементальном обновлении такие строки оставляет, а чистый пересчёт их не создаёт. Сравнивать этот шум истории бессмысленно, нулевые строки надо фильтровать с обеих сторон.

Треть таблиц базы переносить нельзя никогда

Неожиданная цифра из живой УТ: из 8 511 таблиц базы 2 538 - это регистрация изменений планов обмена. 29,8 процента всех таблиц. Что и по каким правилам туда попадает, разобрано в статье о регистрации изменений в плане обмена.

С ними в тесте можно сделать ровно одну вещь: вычистить.

TRUNCATE TABLE [YourTest].dbo._AccumRgChngR40230;   -- очередь регистрации

Копия прода приезжает с живыми узлами обмена. Если долить ей ещё и очередь регистрации, тест при первом же запуске обменов начнёт выгружать данные в боевые базы-приёмники. Это авария, после которой инструмент закапывают вместе с автором.

По той же причине не трогаются пользователи, их настройки и сами планы обмена: это состояние теста, ради сохранения которого всё и затевалось.

Грабли масштаба: то, чего нет на семи объектах

Скрипт для семи объектов прошёл идеально: 923 таблицы, 0 расхождений, 20 секунд. Полный состав конфигурации (1 683 объекта, скрипт на 7,6 миллиона символов) упал дважды, и оба дефекта на малом наборе не воспроизводятся в принципе.

Ошибка 1204: не удалось получить память LOCK. DELETE и INSERT миллионов строк набирают строчных блокировок больше, чем сервер готов держать. Лечится хинтом WITH (TABLOCK) на каждой операции: тест всё равно обновляется монопольно, а INSERT с табличной блокировкой при простой модели восстановления ещё и пишется в журнал минимально.

Журнал транзакций на 22 ГБ в SIMPLE. DELETE регистра сведений на 106 миллионов строк (12,7 ГБ данных) раздул журнал за 22 ГБ и завис на росте файла. Журнал обязан держать всю транзакцию целиком, модель восстановления тут не спасает. Бонусом выяснилось, что откат убитого DELETE стоит примерно столько же, сколько сам DELETE, так что убийство сессии не спасает от ожидания.

Лечение: таблицы, которые заменяются целиком, чистить TRUNCATE - он не пишет в журнал построчно и точно так же откатывается транзакцией. DELETE остаётся только там, где есть WHERE по периоду.

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

Предохранитель, который выглядел работающим

Скрипт сверки заканчивается RAISERROR уровня 16. Я проверил код возврата sqlcmd после заведомо красной сверки и получил ноль.

Без ключа -b sqlcmd возвращает 0 даже при ошибке уровня 16. То есть ночное задание, где сверка стоит первым шагом, посчитает её успешной и выполнит второй шаг - перенос в несовместимую базу. С ключом -b тот же скрипт возвращает 1, и задание останавливается.

sqlcmd -S localhost -E -b -i "C:\sverka\sverka-struktury.sql" -f 65001

Один флажок отделяет предохранитель от декорации, и по умолчанию флажок выключен. На случай, когда про -b всё-таки забыли, в скриптах есть вторая линия обороны: шапка создаёт временную таблицу, и каждый блок первой строкой проверяет, что она существует. Упала шапка - не выполнится ни один блок, с любым набором ключей.

Условие, без которого всё это не работает

Тестовая база обязана быть копией именно этой рабочей, снятой с неё когда-то и восстановленной.

Причина в кодах типов. У составного ссылочного поля три колонки: _TYPE, _RTRef, _RRRef, и _RTRef - внутренний номер таблицы - у баз разного происхождения разный: 0x000002B3 в одной, 0x000002B5 в другой. Одинаковые по смыслу ссылки выглядят разными байтами. Перенесёте как есть - ссылки молча покажут не туда; сверка на такой паре покажет расхождение там, где его нет.

Ровно на этом умер единственный SQL-аналог переноса, который я нашёл: генератор T-SQL 2022 года с 13 скачиваниями за четыре с половиной года. В комментариях к нему претензий про “запись мимо платформы” не было ни одной, вся критика была технической, и главная - как раз про расходящиеся коды типов.

Разные серверы

Если базы лежат на разных SQL Server, между ними нужен linked server. Я ждал, что связь окажется узким местом, и замерил: таблица в 512 037 строк и 57,4 МБ между MS SQL 2022 и MS SQL 2012 на соседней машине прошла через связь за 2,2 секунды против 2,4 у выгрузки через bcp. Разницы на этих объёмах нет, а связь умещает всё в один скрипт вместо трёх шагов с промежуточным файлом.

Проблема пришла со стороны аутентификации. Linked server с Windows-аутентификацией при запуске с третьей машины - это два хопа: клиент, тест, прод. Без настроенного делегирования Kerberos второй хоп падает с ошибкой 18456 “вход пользователя NT AUTHORITY\АНОНИМНЫЙ ВХОД”.

Лечение оно же правильная схема безопасности: на проде заводится SQL-логин с единственной ролью db_datareader, и связь создаётся на тесте с этим логином. Направление принципиально - связь живёт на тесте и смотрит в прод только на чтение. Прав на запись в прод не появляется ни у кого, включая сам инструмент.

Две обработки, которые собирают эти скрипты

Писать такой SQL руками для каждой конфигурации бессмысленно: имена таблиц и колонок отдаёт сама платформа.

// Массив объектов обязателен: без него у служебных таблиц
// поле ИмяТаблицы приходит пустым, и связь итогов с регистром теряется.
Стр = ПолучитьСтруктуруХраненияБазыДанных(МассивОбъектов, Истина);

Обе обработки собирают готовый скрипт по этой карте, показывают его на форме с подсветкой и кладут файлами в указанный каталог. К СУБД они не подключаются: скрипт выполняет администратор, и у вас на руках остаётся ровно тот текст, который будет выполнен.

  • Сверка тестовой базы с рабочей - сравнивает структуру и данные за период, печатает список разошедшихся таблиц и возвращает ненулевой код при расхождении. Ничего не меняет ни в одной базе.
  • Догрузка свежего периода в тестовую базу - собирает скрипт замены периода, пересчёта итогов и зачистки очередей регистрации, со сверкой в конце и ночным заданием SQL Agent в комплекте.

Платформа 8.3, конфигурация любая: обработки работают только с метаданными и структурой хранения, к прикладным объектам не обращаются. Проверено на УТ 11.5.22.67 и MS SQL Server 2022, генерация полного состава конфигурации - 16 секунд.

Если тест переливается за ночь

Тогда всё вышеописанное вам не нужно, и это честный ответ. Для базы, которая восстанавливается за приемлемое время, и для теста, в котором нечего терять, правильный путь - ночное восстановление из свежего бэкапа по расписанию: проще, безопаснее и без единого обращения к таблицам. Готовые скрипты этого пути для MS SQL и PostgreSQL, с предохранителями от затирания прода и переводом в задание планировщика, собраны в отдельном разборе - восстановление базы 1С из бэкапа. Заодно такой тест каждую ночь проверяет, что бэкап рабочей базы вообще восстанавливается, и в регламенте обслуживания базы 1С мы ставим его ровно для этого.

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

Что прогонять на свежей копии

Если копию потом откроет подрядчик, сначала уберите из неё живые ФИО и ИИН: где они прячутся в базе и как их заменить с возвратом обратно, показывает анонимизатор базы. Помимо основных операций проверьте клиентские команды передаваемых обработок. Приёмка управляемой формы включает пустое состояние, отмену диалога и повторный запуск. Для работ с удалением на той же копии выполняется оценка сроков и последствий очистки. Если на копии надо проверить что-то ночное, учтите, что регламентные задания там обычно молчат: после смены строки соединения БСП считает базу копией, а на кластере у теста часто стоит блокировка заданий. Что именно держит каждое задание, покажет проверка, почему регламентное задание не выполняется.

Тестовая база отстала, и обновляться страшно

Настроим ночное обновление копии из бэкапа рабочей, чтобы проверять на свежих данных, а не на прошлогодних

Құны
450 000 ₸-денбір реттік баптау; сүйемелдеу - айына 150 000 ₸-ден; салықтарсыз
Мерзімі
2-3 дня

Жазуға ерте ме? Өзіңіз өлшеп көріңіз

Тестовая база отстала от рабочей на полгода

Өңдеуді ашу

INFOSTART TECH EVENT 2026: конференция для 1с-специалистов. если собираетесь, регистрируйтесь по нашей ссылке. Регистрация →

Мұны да оқыңыз

База 1С не открывается после обновления конфигурации: порядок действий, когда копии нет

Конфигуратор пишет "При обновлении данных, после последней реструктуризации, произошла критическая ошибка", пользователи в базу не попадают, копии до обновления нет. Разбираем по шагам: что сохранить первым, как по журналу регистрации найти этап, на котором упало, какие действия только добавляют ошибок, когда бросать починку старой базы и как через ibcmd выгрузить .dt из базы, которая не открывается, собрать новую и тихо подменить её в кластере. В конце - задания резервного копирования, после которых такая авария превращается в откат на минуту до обновления.

Остатки в 1С не сходятся с движениями: как найти сломанный месяц итогов и не пересчитывать всю историю

Отчёт по движениям даёт одно число, запрос к остаткам другое, перепроведение ничего не меняет. Разбираем, как отличить сломанные итоги от ошибки в движениях, каким запросом найти позицию и месяц расхождения, почему строку итогов на 1 августа чинит пересчёт за август, а не за июль, в каком порядке пересчитывать текущие итоги и как посмотреть саму строку итогов на SQL. Все шаги проверены на тестовой копии УТ 11.5 с испорченной строкой.

CHECKDB нашёл ошибки в базе 1С: порядок действий до REPAIR и после

CHECKDB советует repair_allow_data_loss и на битый индекс, и на потерянные документы. Разбираем по шагам, что делать в первый час: что сохранить до исправления, как по таблице и номеру индекса понять, что задето в 1С, как перестроить битый индекс без потерь и как получить список пропавших документов, пока REPAIR не удалил их молча. Проверено на копии УТ для Казахстана с испорченными страницами.