Рано или поздно любой администратор или разработчик, сопровождающий базы данных на платформе 1С:Предприятие 7.7, сталкивается с проблемой ограничения формата DBF. Когда размер одной из таблиц (чаще всего это таблица проводок 1SENTRY.DBF или таблица движений регистров 1SPART.DBF) приближается к критической отметке в 1 Гб или, тем более, 2 Гб, система начинает вести себя нестабильно: появляются регулярные сбои индексов, зависания транзакций и риск полной потери данных. Использование временных утилит вроде kernel33 лишь оттягивает неизбежный финал.
В этой статье мы подробно разберем, как продлить жизнь вашей информационной базе 1С 7.7, переведя ее на СУБД MS SQL Server 2012 или более современную версию. Мы детально рассмотрим технические нюансы, установку необходимых патчей, решение системных ошибок СУБД и оптимизацию быстродействия, которая критически важна после переезда.
---
Платформа 1С 7.7 разрабатывалась во времена господства MS SQL Server 2000 (версия СУБД 8.0). Разработчики жестко привязали платформу к возможностям этой СУБД. При запуске 1С 7.7 выполняет несколько проверок:
sp_dboption, которая использовалась в SQL 2000 для настройки параметров баз данных. Начиная с SQL Server 2012, корпорация Microsoft полностью удалила эту процедуру из системной базы данных master, из-за чего запуск 1С падает с критической ошибкой базы данных.Для того чтобы обойти эти искусственные и архитектурные ограничения, нам придется применить комплекс мер: использовать неофициальные патчи платформы, восстановить в SQL Server недостающие процедуры и оптимизировать структуру метаданных информационной базы.
---
Для обхода проверки версии SQL-сервера и корректной работы с современными драйверами ODBC используется модифицированная библиотека BkEnd.dll (в сообществе разработчиков известная как проект Solution7 или неофициальный релиз платформы 27.7).
Разберем, что делает этот патч и как его применить:
BkEnd.dll в каталоге установленной платформы 1С 7.7 (обычно это C:\Program Files (x86)\1Cv77\bin\) на модифицированную версию. Также в зависимости от сборки патча в этот же каталог могут копироваться вспомогательные библиотеки (например, BkEndUtls.dll).Важное требование безопасности в SQL Server: Пользователю СУБД, под которым 1С 7.7 будет подключаться к базе данных, помимо стандартной роли владельца базы db_owner, обязательно нужно выдать системное разрешение VIEW SERVER STATE. Без этого патч Solution7 не сможет опрашивать СУБД на предмет количества активных блокировок и подключений, что приведет к ошибкам монопольного доступа при входе пользователей.
---
Если вы разворачиваете базу на MS SQL Server 2012 или более новом сервере, то даже при использовании патченной библиотеки платформа упадет при первом старте, так как не найдет процедуру sp_dboption в базе master.
Проанализируем решение этой проблемы. Нам необходимо вручную воссоздать эту системную процедуру, которая будет перехватывать вызовы от 1С 7.7 и транслировать их в современные команды SQL-диалекта T-SQL (в частности, в конструкцию ALTER DATABASE).
Для этого запустите среду SQL Server Management Studio (SSMS), подключитесь к вашему серверу под учетной записью администратора (sa), выберите системную базу данных master, создайте новый запрос и выполните следующий SQL-скрипт:
USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [dbo].[sp_dboption]
@dbname sysname = NULL,
@optname varchar(35) = NULL,
@optvalue varchar(10) = NULL
as
set nocount on
declare @dbid int
declare @catvalue int
declare @optcount int
declare @allstatopts int
declare @alloptopts int
declare @allcatopts int
declare @exec_stmt nvarchar(max)
declare @fulloptname varchar(35)
declare @alt_optname varchar(50)
declare @alt_optvalue varchar(30)
declare @optnameIn varchar(35)
select @optnameIn = @optname
,@optname = LOWER (@optname collate Latin1_General_CI_AS)
select @allstatopts=number from master.dbo.spt_values where type = 'D'
and name = 'ALL SETTABLE OPTIONS'
select @allcatopts=number from master.dbo.spt_values where type = 'DC'
and name = 'ALL SETTABLE OPTIONS'
select @alloptopts=number from master.dbo.spt_values where type = 'D2'
and name = 'ALL SETTABLE OPTIONS'
if @dbname is null
begin
select 'Settable database options:' = name
from master.dbo.spt_values
where (type = 'D'
and number & @allstatopts <> 0
and number not in (0,@allstatopts))
or (type = 'DC'
and number & @allcatopts <> 0
and number not in (0,@allcatopts))
or (type = 'D2'
and number & @alloptopts <> 0
and number not in (0,@alloptopts))
order by name
return (0)
end
select @dbid = dbid
from master.dbo.sysdatabases
where name = @dbname
if @dbid is null
begin
raiserror(15010,-1,-1,@dbname)
print ' '
select 'Available databases:' = name
from master.dbo.sysdatabases
return (1)
end
if @optname is null
begin
select 'The following options are set:' = v.name
from master.dbo.spt_values v, master.dbo.sysdatabases d
where d.name=@dbname
and ((number & @allstatopts <> 0
and number not in (-1,@allstatopts)
and v.type = 'D'
and (v.number & d.status)=v.number)
or (number & @allcatopts <> 0
and number not in (-1,@allcatopts)
and v.type = 'DC'
and d.category & v.number <> 0)
or (number & @alloptopts <> 0
and number not in (-1,@alloptopts)
and v.type = 'D2'
and d.status2 & v.number <> 0))
return(0)
end
if @optvalue is not null and lower(@optvalue) not in ('true', 'false', 'on', 'off')
begin
raiserror(15241,-1,-1)
return (1)
end
select @optcount = count(*) ,@fulloptname = min(name)
from master.dbo.spt_values
where lower(name collate Latin1_General_CI_AS) like '%' + @optname + '%'
and ((type = 'D'
and number & @allstatopts <> 0
and number not in (-1,@allstatopts))
or (type = 'DC'
and number & @allcatopts <> 0
and number not in (-1,@allcatopts))
or (type = 'D2'
and number & @alloptopts <> 0
and number not in (-1,@alloptopts)))
if @optcount = 0
begin
raiserror(15011,-1,-1,@optnameIn)
print ' '
select 'Settable database options:' = name
from master.dbo.spt_values
where (type = 'D'
and number & @allstatopts <> 0
and number not in (-1,@allstatopts))
or (type = 'DC'
and number & @allcatopts <> 0
and number not in (-1,@allcatopts))
or (type = 'D2'
and number & @alloptopts <> 0
and number not in (-1,@alloptopts))
order by name
return (1)
end
if @optcount > 1
begin
raiserror(15242,-1,-1,@optnameIn)
print ' '
select duplicate_options = name
from master.dbo.spt_values
where lower(name collate Latin1_General_CI_AS) like '%' + @optname + '%'
and ((type = 'D'
and number & @allstatopts <> 0
and number not in (-1,@allstatopts))
or (type = 'DC'
and number & @allcatopts <> 0
and number not in (-1,@allcatopts))
or (type = 'D2'
and number & @alloptopts <> 0
and number not in (-1,@alloptopts))
)
return (1)
end
if @optvalue is null
begin
select OptionName = v.name,
CurrentSetting = (case
when ( ((v.number & d.status) = v.number
and v.type = 'D')
or (d.category & v.number <> 0
and v.type = 'DC')
or (d.status2 & v.number <> 0
and v.type = 'D2')
)
then 'ON'
when not
( ((v.number & d.status) = v.number
and v.type = 'D')
or (d.category & v.number <> 0
and v.type = 'DC')
or (d.status2 & v.number <> 0
and v.type = 'D2')
)
then 'OFF'
end)
from master.dbo.spt_values v, master.dbo.sysdatabases d
where d.name=@dbname
and ((v.number & @allstatopts <> 0
and v.number not in (-1,@allstatopts)
and v.type = 'D')
or (v.number & @allcatopts <> 0
and v.number not in (-1,@allcatopts)
and v.type = 'DC')
or (v.number & @alloptopts <> 0
and v.number not in (-1,@alloptopts)
and v.type = 'D2')
)
and lower(v.name) = lower(@fulloptname)
return (0)
end
select @catvalue = 0
select @catvalue = number
from master.dbo.spt_values
where lower(name) = lower(@fulloptname)
and type = 'DC'
if (@catvalue <> 0)
begin
select @alt_optvalue = (case lower(@optvalue)
when 'true' then 'true'
when 'on' then 'true'
else 'false'
end)
select @alt_optname = (case @catvalue
when 1 then 'publish'
when 2 then 'subscribe'
when 4 then 'merge publish'
else quotename(@fulloptname, '''')
end)
select @exec_stmt = quotename(@dbname, '[') + '.dbo.sp_replicationdboption'
EXEC @exec_stmt @dbname, @alt_optname, @alt_optvalue
return (0)
end
select @alt_optvalue = (case lower(@optvalue)
when 'true' then 'ON'
when 'on' then 'ON'
else 'OFF'
end)
select @fulloptname = lower(@fulloptname)
select @alt_optname = (case @fulloptname
when 'auto create statistics' then 'AUTO_CREATE_STATISTICS'
when 'auto update statistics' then 'AUTO_UPDATE_STATISTICS'
when 'autoclose' then 'AUTO_CLOSE'
when 'autoshrink' then 'AUTO_SHRINK'
when 'ansi padding' then 'ANSI_PADDING'
when 'arithabort' then 'ARITHABORT'
when 'numeric roundabort' then 'NUMERIC_ROUNDABORT'
when 'ansi null default' then 'ANSI_NULL_DEFAULT'
when 'ansi nulls' then 'ANSI_NULLS'
when 'ansi warnings' then 'ANSI_WARNINGS'
when 'concat null yields null' then 'CONCAT_NULL_YIELDS_NULL'
when 'cursor close on commit' then 'CURSOR_CLOSE_ON_COMMIT'
when 'torn page detection' then 'TORN_PAGE_DETECTION'
when 'quoted identifier' then 'QUOTED_IDENTIFIER'
when 'recursive triggers' then 'RECURSIVE_TRIGGERS'
when 'default to local cursor' then 'CURSOR_DEFAULT'
when 'offline' then (case @alt_optvalue when 'ON' then 'OFFLINE' else 'ONLINE' end)
when 'read only' then (case @alt_optvalue when 'ON' then 'READ_ONLY' else 'READ_WRITE' end)
when 'dbo use only' then (case @alt_optvalue when 'ON' then 'RESTRICTED_USER' else 'MULTI_USER' end)
when 'single user' then (case @alt_optvalue when 'ON' then 'SINGLE_USER' else 'MULTI_USER' end)
when 'select into/bulkcopy' then 'RECOVERY'
when 'trunc. log on chkpt.' then 'RECOVERY'
when 'db chaining' then 'DB_CHAINING'
else @alt_optname
end)
if @fulloptname = 'dbo use only'
begin
if @alt_optvalue = 'ON'
begin
if databaseproperty(@dbname, 'IsSingleUser') = 1
begin
raiserror(5066,-1,-1);
return (1)
end
end
else
begin
if databaseproperty(@dbname, 'IsDBOOnly') = 0
return (0)
end
end
if @fulloptname = 'single user'
begin
if @alt_optvalue = 'ON'
begin
if databaseproperty(@dbname, 'ISDBOOnly') = 1
begin
raiserror(5066,-1,-1);
return (1)
end
end
else
begin
if databaseproperty(@dbname, 'IsSingleUser') = 0
return (0)
end
end
select @alt_optvalue = (case @fulloptname
when 'default to local cursor' then (case @alt_optvalue when 'ON' then 'LOCAL' else 'GLOBAL' end)
when 'offline' then ''
when 'read only' then ''
when 'dbo use only' then ''
when 'single user' then ''
else @alt_optvalue
end)
if lower(@fulloptname) = 'select into/bulkcopy'
begin
if @alt_optvalue = 'ON'
begin
if databaseproperty(@dbname, 'IsTrunclog') = 1
select @alt_optvalue = 'RECMODEL_70BACKCOMP'
else
select @alt_optvalue = 'BULK_LOGGED'
end
else
begin
if databaseproperty(@dbname, 'IsTrunclog') = 1
select @alt_optvalue = 'SIMPLE'
else
select @alt_optvalue = 'FULL'
end
end
if lower(@fulloptname) = 'trunc. log on chkpt.'
begin
if @alt_optvalue = 'ON'
begin
if databaseproperty(@dbname, 'IsBulkCopy') = 1
select @alt_optvalue = 'RECMODEL_70BACKCOMP'
else
select @alt_optvalue = 'SIMPLE'
end
else
begin
if databaseproperty(@dbname, 'IsBulkCopy') = 1
select @alt_optvalue = 'BULK_LOGGED'
else
select @alt_optvalue = 'FULL'
end
end
select @exec_stmt = 'ALTER DATABASE ' + quotename(@dbname) + ' SET ' + @alt_optname + ' ' + @alt_optvalue + ' WITH NO_WAIT'
EXEC (@exec_stmt)
if @@error <> 0
begin
raiserror(15627,-1,-1)
return (1)
end
return (0)
GO
Этот скрипт пересоздает процедуру sp_dboption, адаптируя ее к системной архитектуре SQL Server 2012 и выше, что решает проблему инициализации базы данных 1С 7.7.
---
Рассмотрим по шагам процесс непосредственной миграции данных из формата DBF на SQL:
.dat.
Обратите внимание: Если объем вашей исходной DBF-базы превышает 1.5–2 Гб, штатный конфигуратор 1С 7.7 может вылетать без ошибок в процессе создания .dat файла. В этом случае используйте стороннюю проверенную утилиту Unload_Dat_Fix, разработанную сообществом для корректного обхода лимита памяти процесса выгрузки.
db_1c77). Настройте для файлов данных и лога транзакций (MDF и LDF) достаточный размер первоначального выделения памяти и шаг авторасширения (например, 512 Мб), чтобы СУБД не тратила процессорное время на постоянное фрагментированное довыделение дискового пространства в процессе загрузки.1cv7.md, users.usr в каталоге usrdef, файлы конфигурации регламентированных отчетов и папка ExtForms). Все DBF и CDX файлы можно удалить — они больше не понадобятся. Запустите конфигуратор этой пустой базы, выберите "Администрирование" -> "Параметры базы данных SQL" и заполните параметры авторизации:
.dat. Конфигуратор создаст всю необходимую структуру таблиц в SQL-базе и заполнит их данными.---
Переведя базу на SQL Server, вы сразу заметите парадоксальное явление: некоторые отчеты и групповые обработки (особенно групповое перепроведение документов) начали работать значительно медленнее, чем это было в локальном DBF-варианте. Разберем по шагам причины этого замедления и методы исправления ситуации.
Штатный механизм запросов 1С 7.7 ("черные запросы") устроен по принципу последовательной выборки данных. Платформа делает огромное количество мелких запросов к СУБД, нагружая сеть и процессор сервера очередью транзакций. Для решения этой проблемы рекомендуется переписать ключевые отчеты и тяжелые алгоритмы проведения документов на прямые запросы T-SQL с использованием внешней компоненты 1С++.
Прямой запрос обращается напрямую к таблицам SQL Server, минуя транслятор 1С, и за один такт возвращает готовую выборку. Посмотрим на сравнительный пример кода.
Штатный "черный запрос" в 1С 7.7:
// Черный запрос работает крайне медленно на SQL
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(СкладскойОстаток)
|Период с ВыбДата по ВыбДата;
|Товар = Регистр.ОстаткиТМЦ.Товар;
|Склад = Регистр.ОстаткиТМЦ.Склад;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КонОст = КонОст(Количество);
|Группировка Товар без групп;
|Условие(Склад = ВыбСклад);
|//}}ЗАПРОС";
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
Пока Запрос.Группировка(1) = 1 Цикл
// Обработка данных
КонецЦикла;
КонецЕсли;
Оптимизированный прямой запрос с использованием внешней компоненты 1С++:
// Прямой SQL-запрос выполняется в десятки раз быстрее
ODBCРекордсет = СоздатьОбъект("ODBCRecordset");
ТекстЗапроса = "
|SELECT
| Рег.SP112 as [Товар $Справочник.Номенклатура],
| SUM(Рег.SP115) as Количество
|FROM
| $РегистрИтоги.ОстаткиТМЦ as Рег
|WHERE
| Рег.period = :ВыбДата
| AND Рег.SP113 = :ВыбСклад
|GROUP BY
| Рег.SP112
|";
ODBCРекордсет.УстановитьТекстовыйПараметр("ВыбДата", НачалоМесяца(ВыбДата));
ODBCРекордсет.УстановитьТекстовыйПараметр("ВыбСклад", ВыбСклад);
ТаблицаРезультатов = ODBCРекордсет.ВыполнитьИнструкцию(ТекстЗапроса);
// В ТаблицаРезультатов мгновенно возвращается готовая таблица значений
В DBF-версии порядок следования измерений в регистрах практически не влиял на скорость работы. В SQL-версии от этого напрямую зависит структура создаваемых СУБД индексов.
Если в вашем регистре первым измерением стоит реквизит, по которому редко накладывается фильтр (например, "Проект" или "МОЛ"), а вторым — часто используемый "Товар", то при попытке получить остатки по конкретному товару SQL Server не сможет использовать индекс эффективно. Он будет вынужден делать операцию Table Scan (сканирование всей таблицы), что приведет к колоссальным задержкам.
Проанализируйте ваши регистры накопления в Конфигураторе. Измените порядок измерений так, чтобы первыми шли наиболее часто выбираемые и детализированные измерения (например, сначала "Товар", затем "Склад", и только потом — вспомогательные свойства).
В DBF-версиях разработчики часто использовали метод получения остатков ВыгрузитьИтоги() или ПолучитьИтог() внутри циклов по строкам проводимого документа. На SQL-платформе такое проведение будет "умирать", так как каждая строка вызывает обращение к серверу через сеть.
Измените логику алгоритмов проведения документов:
ПолучитьПараметрыТаблицы() или предварительно проиндексировав таблицу значений (например, средствами класса ИндексированнаяТаблицаЗначений из ВК 1С++).Если вы развернули базу на MS SQL Server 2014, 2016 или новее, вы можете столкнуться с резким замедлением некоторых стандартных отчетов. Это связано с внедрением нового оптимизатора запросов (Cardinality Estimator).
Для баз 1С 7.7 рекомендуется принудительно вернуть СУБД к старому стилю оценки стоимости запросов. Для этого в свойствах базы данных выполните инструкцию:
ALTER DATABASE [db_1c77]
SET COMPATIBILITY_LEVEL = 110; -- Соответствует уровню SQL 2012
GO
ALTER DATABASE SCOPED CONFIGURATION
SET LEGACY_CARDINALITY_ESTIMATION = ON;
GO
Или запустите SQL Server с глобальным флагом трассировки 9481, который отключает новый оптимизатор на уровне всего экземпляра сервера.
---
Еще одна известная системная проблема при работе 1С 7.7 на SQL — медленная выборка или падение системы при выполнении метода ВыбратьПодчиненныеДокументы() без явного указания временного интервала.
Чтобы избежать этой проблемы, выполните глобальный поиск в вашей конфигурации по тексту .ВыбратьПодчиненныеДокументы( и принудительно задайте границы выборки, ограничив её разумными рамками:
// Вместо небезопасного вызова:
// Док.ВыбратьПодчиненныеДокументы();
// Используем явное ограничение периода:
Док.ВыбратьПодчиненныеДокументы(РабочаяДата() - 365, РабочаяДата() + 30);
Это убережет систему от сканирования всей таблицы связей документов с начала ведения учета.
---
Если развертывание полноценного MS SQL Server кажется вам избыточным или дорогим с точки зрения лицензирования, можно рассмотреть следующие альтернативы, позволяющие уйти от ограничений DBF:
1С++ и модуля 1SQLite вы можете перевести базу в формат SQLite. База будет храниться в одном файле, лимит размера таблиц составит до нескольких терабайт, при этом не потребуется поднимать тяжелый сервер СУБД. Это идеальное решение для небольших баз данных (до 5–10 пользователей).SELTA, который позволяет 1С 7.7 работать с бесплатной СУБД PostgreSQL. Данное решение отлично зарекомендовало себя в условиях импортозамещения и экономии на лицензиях Windows/MS SQL.Используя приведенные инструкции, вы сможете перевести свою информационную базу 1С 7.7 на SQL Server 2012 и выше, обеспечив ей стабильную работу и надежность хранения данных на многие годы вперед.