CHECKDB нашёл ошибки в базе 1С: порядок действий до REPAIR и после
Иван Недомолков · Chop etilgan:
Утро, ночная проверка целостности упала с ошибками, администратор пересылает вывод
DBCC CHECKDB и спрашивает: “откатываемся на вчера?”. В выводе служебные имена таблиц,
номера страниц и одна и та же рекомендация в конце: repair_allow_data_loss. По ней
кажется, что данные теряются в любом случае.
Это не так. Ту же рекомендацию CHECKDB пишет и тогда, когда испорчен только индекс для поиска, где строк 1С нет вовсе. На нашем стенде из четырёх испорченных страниц копия из бэкапа понадобилась для двух, и даже для них откат всей базы на вчера был не нужен: хватило списка конкретных документов.
Ниже порядок, по которому мы проходим такой случай. Каждый шаг проверен на стенде, о нём в конце статьи.
Шаг 0. Чего не делать в первые минуты
Три действия кажутся очевидными и стоят дороже всего:
| Действие | Почему не сейчас |
|---|---|
Запустить REPAIR_ALLOW_DATA_LOSS | удаляет строки битых страниц и не называет, какие документы удалил. Список после этого собрать можно только по копии |
| Перебирать документы в 1С, “какой не откроется” | каждое чтение битой страницы обрывает вызов ошибкой СУБД 824. Толку мало, нервов много |
| Восстановить всю базу из бэкапа поверх рабочей | вы теряете всё, что введено после бэкапа, ради нескольких документов, которые можно вернуть точечно |
Что сделать сразу: сохранить вывод CHECKDB целиком в файл (вкладка “Сообщения” в SSMS
или вывод sqlcmd) и снять полный бэкап базы как есть, с повреждением. Он пригодится,
если исправление удалит больше, чем ожидали.
Шаг 1. Найти в выводе таблицу и номер индекса
Из всего вывода для решения нужны два поля каждой битой страницы. Имя таблицы стоит в итоговой строке “в таблице …”, номер индекса в самих сообщениях (“идентификатор индекса 1”). Если итоговые строки потерялись при копировании, имя и индекс по числам даст сам SQL Server:
-- Числа берутся из сообщения CHECKDB: идентификатор объекта и идентификатор индекса
DECLARE @obj int = 680389493;
SELECT OBJECT_NAME(@obj) AS table_name,
i.index_id,
i.name AS index_name,
i.type_desc
FROM sys.indexes AS i
WHERE i.object_id = @obj
ORDER BY i.index_id;
Как читать номер индекса:
| index id | Что это | Что лежит |
|---|---|---|
| 1 | кластерный индекс | сами строки таблицы: шапки документов, строки табличных частей, движения |
| больше 1 | некластерный индекс | копия нескольких полей для поиска, по дате или номеру. Строк 1С нет |
| 0 | таблица без кластерного индекса | в базах 1С редкость. По соседним страницам не разобрать, только сверкой с копией |
Ещё пригодятся номера соседних страниц. В сообщениях 8976 и 8978 CHECKDB называет страницу до испорченной и после неё, по ним потом собирается список документов.
Шаг 2. Перевести таблицу в объект 1С
Имя вида _Document520 в объект метаданных переводит сама платформа. Запускаем
в любой консоли кода под пользователем с полными правами:
ИмяТаблицы = "_Document520"; // из итоговой строки CHECKDB
// Пустой первый параметр - вся конфигурация, Истина - имена как в SQL Server
Для Каждого Описание Из ПолучитьСтруктуруХраненияБазыДанных(, Истина) Цикл
Если НРег(Описание.ИмяТаблицыХранения) = НРег(ИмяТаблицы) Тогда
Сообщить(Описание.Метаданные + " (" + Описание.Назначение + ")");
Для Каждого Индекс Из Описание.Индексы Цикл
Сообщить(" индекс " + Индекс.ИмяИндексаХранения);
КонецЦикла;
КонецЕсли;
КонецЦикла;
На стенде ответ был Документ.РеализацияТоваровУслуг (Основная) и восемь индексов,
среди них кластерный _Document520HPK и _Document520_ByDocDate по дате. Функция
платформенная, работает в любой конфигурации, с БСП и без неё.
Шаг 3. Вердикт по виду таблицы
Поле Назначение из шага 2 и номер индекса из шага 1 вместе дают решение. Сводная
таблица, по которой мы сортируем битые страницы:
| Что задето | Вердикт | Чем восстанавливать |
|---|---|---|
| некластерный индекс (index id больше 1) | данные целы | перестроить индекс, шаг 4 |
| движения регистра накопления, бухгалтерии, расчёта | без бэкапа | перепровести документы-регистраторы, шаг 5 |
итоги регистра (Итоги) | без бэкапа | пересчёт итогов штатными средствами |
регистрация изменений для обмена (РегистрацияИзменений) | без бэкапа | заново зарегистрировать изменения для узла или полная синхронизация |
шапки документов, справочников, планов счетов (Основная) | нужна копия | вернуть объекты из копии по списку, шаг 6 |
табличная часть (ТабличнаяЧасть) | нужна копия | документ возвращается из копии целиком |
| независимый регистр сведений, константа | нужна копия | записи вводились руками, пересчитать не из чего |
| таблицы, где платформа хранит конфигурацию | разбирать вручную | база может не открыться вовсе, это работа администратора и бэкапа |
Оговорка к движениям: ручные корректировки движений проведение не вернёт. Если в вашей конфигурации регистр заполняют руками, читайте “без бэкапа” для него как “нужна копия”.
Оговорка к итогам: пересчитывать всю историю после битой страницы не обязательно. Чек-ап итогов и остатков 1С сверяет итоги с движениями по каждой границе месяца, находит первый месяц, где они разошлись, и строит пересчёт за один-два месяца от него, а потом проверяет результат.
Шаг 4. Битый индекс: перестроить в правильном порядке
Первый случай на стенде: испорчена страница индекса _Document520_ByDocDate. CHECKDB
выдал те же четыре сообщения и ту же рекомендацию repair_allow_data_loss, что
и на потерянных документах. Строк при этом не пропало ни одной.
Простой ALTER INDEX ... REBUILD упал с той же ошибкой 824: при перестроении SQL Server
читает старый индекс и спотыкается о битую страницу. Сработал порядок “отключить,
потом построить”:
-- Отключённый индекс строится заново из таблицы, старые страницы не читаются
ALTER INDEX [_Document520_ByDocDate] ON [dbo].[_Document520] DISABLE;
ALTER INDEX [_Document520_ByDocDate] ON [dbo].[_Document520] REBUILD;
-- Повторная проверка
DBCC CHECKDB WITH NO_INFOMSGS, ALL_ERRORMSGS;
После этого CHECKDB чистый, в индексе 3 000 строк, в таблице тоже 3 000. Делайте это без пользователей: пока индекс отключён, запросы по дате идут медленнее, а уникальный индекс не следит за уникальностью. Штатную реиндексацию из тестирования и исправления на битом индексе мы не проверяли.
Шаг 5. Движения регистра: какие документы перепровести
Второй случай: страница регистра “Товары на складах”, на ней 44 движения десяти
документов, восьми реализаций и двух приобретений. Для самих движений бэкап не нужен:
их создаёт проведение. Нужен только список регистраторов. В строке регистра
регистратор записан двумя полями: _RecorderTRef с номером таблицы документа
(у реализаций на стенде 520, как в _Document520) и _RecorderRRef со ссылкой.
Здесь стенд показал неожиданную вещь. Кластерный индекс этого регистра в УТ упорядочен по периоду, и граница страницы режет документ посередине: у РТ-001481 строки 2 и 3 лежали на битой странице, а строка 1 на соседней, целой. В список на перепроведение он попадает, но “пропали все движения” про него сказать нельзя.
Порядок такой: битая страница убирается (шаг 7), документы из списка перепроводятся. Если документ потерян целиком, сначала он возвращается из копии, потом проводится: без регистратора проводить нечего.
Шаг 6. Шапки и табличные части: список документов до исправления
Третий и четвёртый случаи: страница самих реализаций и страница их табличной части “Товары”. Пересчитать их не из чего, нужна копия из бэкапа. Вопрос только в том, какие документы из неё брать, и ответить на него можно двумя путями.
По соседним страницам. Строки таблицы лежат по страницам в порядке ключа. Соседние страницы целые, их читает администратор, и последний ключ предыдущей и первый ключ следующей задают диапазон, который был на битой странице:
-- Нужны права sysadmin. 0 - текущая база, 1 - номер файла, дальше номер страницы
-- из сообщений 8976/8978. Режим 3 показывает значения колонок по слотам.
DBCC PAGE (0, 1, 136101, 3) WITH TABLERESULTS; -- страница до битой
DBCC PAGE (0, 1, 136103, 3) WITH TABLERESULTS; -- страница после битой
Сами строки диапазона есть в любом уцелевшем некластерном индексе той же таблицы: каждый некластерный индекс хранит полный ключ кластерного. Запрос с явным указанием индекса берёт поля из него и в кластерный индекс не спускается, поэтому битую страницу никто не читает:
-- Схема запроса. Границы - ключи с соседних страниц из DBCC PAGE.
-- Выбирать только поля, которые есть в индексе, иначе запрос пойдёт за ними
-- в кластерный индекс и упадёт на битой странице.
SELECT a._IDRRef, a._Date_Time
FROM dbo._Document520 AS a WITH (INDEX([_Document520_ByDocDate]))
WHERE a._IDRRef > 0x... -- последний ключ предыдущей страницы
AND a._IDRRef < 0x... -- первый ключ следующей страницы
OPTION (MAXDOP 1);
Отдельно мы обнулили у страницы реализаций весь заголовок, 96 байт. Вместо 8939 пришло сообщение 8909 с объектом 0: страница больше не знает, чья она. Путь по соседям всё равно дал те же девять документов.
Сверкой с копией средствами 1С. Последний бэкап до сбоя поднимается отдельной информационной базой. В обеих базах снимается слепок обычными запросами: ссылки на объекты, число строк табличной части и движений по каждому владельцу. Что есть в копии и пропало в рабочей, то и потеряно. На стенде слепок документа и регистра, 10 499 строк, снимался 0,7 секунды на сервере, сравнение шло 1,2-1,4 секунды.
Оба пути дали одно и то же, и это совпало с тем, что лежало на страницах до порчи:
| Страница | До порчи | Нашлось |
|---|---|---|
| реализации | 9 документов, РТ-001342 … РТ-001350 | 9, те же |
| табличная часть “Товары” | 19 строк у 5 документов, РТ-001434 … РТ-001438 | 19 у 5 |
| регистр “Товары на складах” | 44 движения у 10 документов | 44 у 10 |
Ключ из SQL Server приходит в виде 16 байт, а в 1С это уникальный идентификатор с переставленными байтами. Перестановку мы сверили по документу с целой страницы:
Двоичное = "0x978EAC120374CE0811F1BA452A5281FD"; // _IDRRef документа РТ-001400
Шестн = НРег(Сред(Двоичное, 3));
УИД = Сред(Шестн, 25, 8) + "-" + Сред(Шестн, 21, 4) + "-" + Сред(Шестн, 17, 4)
+ "-" + Лев(Шестн, 4) + "-" + Сред(Шестн, 5, 12);
// УИД = "2a5281fd-ba45-11f1-978e-ac120374ce08", совпал с идентификатором в 1С
Ссылка = Документы.РеализацияТоваровУслуг.ПолучитьСсылку(Новый УникальныйИдентификатор(УИД));
В рабочей базе реквизиты такой ссылки читать нельзя, страница битая. Её место в копии: по идентификатору документ находится однозначно, даже если номер повторяется в разных годах.
Шаг 7. Исправление и то, что после него остаётся
Только когда список снят и бэкап с повреждением лежит рядом, администратор исправляет
базу. На отдельной копии, где испорчены все четыре страницы сразу, мы прогнали
REPAIR_ALLOW_DATA_LOSS и сравнили результат с копией до порчи.
REPAIR удалил ровно содержимое битых страниц: девять реализаций, 19 строк табличной части у пяти документов, 44 движения у десяти. Индекс по дате перестроил сам. Отчитался строкой “страница освобождена”, без единого номера документа.
Главная ловушка этого шага: у девяти удалённых реализаций их строки “Товаров” и движения лежали на других, целых страницах, и REPAIR их не тронул. Это 41 строка табличной части и 41 движение по складу. База открывается, повторный CHECKDB чистый, а остатки на складе считаются в том числе по документам, которых уже нет. Мы нашли эти строки запросом к копии. Поймает ли их штатное тестирование и исправление, не проверяли.
Если у базы модель восстановления FULL и цепочка бэкапов журнала непрерывна, есть
путь мягче REPAIR: SQL Server умеет вернуть из бэкапа отдельные страницы,
RESTORE DATABASE ... PAGE. На стенде модель SIMPLE, этот путь мы не проверяли.
Дальше документы из списка переносятся из копии штатно (выгрузка и загрузка данных XML по списку), документы с пропавшими движениями перепроводятся. Документ из копии вернётся в состоянии на момент бэкапа: правки, сделанные после, повторяют руками. Документ, введённый после бэкапа и потерянный на битой странице, сверка не увидит, в копии его нет. Такие ищут по первичке и журналу регистрации за период между бэкапом и сбоем.
На чём это проверено
Копия пустой типовой УТ для Казахстана 3.4 на MS SQL Server 2022, платформа 8.3.27.
Данные синтетические: 3 000 реализаций и 1 500 приобретений товаров, у каждого документа
от двух до шести строк “Товаров” и движения по регистру “Товары на складах”. Страницы
портились недокументированной командой DBCC WRITEPAGE в однопользовательском режиме:
восемь байт мимо кэша, контрольная сумма перестаёт сходиться, для SQL Server это выглядит
как сбой диска. Четыре случая по отдельности, каждый с чистой копии, и пятый со всеми
четырьмя страницами сразу. Что лежало на каждой странице до порчи, записано заранее.
Чего мы не проверяли: несколько битых страниц подряд (соседка тоже нечитаема, граница берётся дальше), битую страницу промежуточного уровня индекса, файловые базы и PostgreSQL. Перевод таблиц в объекты платформенный, а сам разбор гонялся только на УТ для Казахстана.
Готовая обработка
Шаги 1-3 и сверку из шага 6 мы собрали во внешнюю обработку “Повреждение базы 1С на MS SQL: что задето и что поднимать из бэкапа”, она продаётся на Инфостарте за 10 SM. На вход вывод CHECKDB как есть, на выходе объекты 1С с цветным вердиктом по каждой битой странице и план восстановления. Список пропавших документов с номерами и датами даёт сверка рабочей базы с копией из бэкапа. К SQL Server обработка не подключается и в базу не пишет. Что видно в её отчёте на стенде, показано на странице инструмента.
Чтобы такой разбор не пришлось делать утром после аварии, CHECKDB стоит гонять по расписанию, лучше на копии из бэкапа: как это встроить в регламент, разобрано в статье про регламентное обслуживание СУБД под 1С, а восстановление копии с предохранителями в разборе восстановления базы 1С из бэкапа SQL. Проверить, что сервер и бэкапы под базой настроены правильно, помогает Чек-ап СУБД.