Сравнение 5 различных методов доступа к базе данных 1С
Не так давно на этом сайте были размещены результаты тестирования вариантов доступа к данным на примере rainbow
и технологии ADO
. Тестирование проводилось на достаточно сложных запросах, которые нечасто встречаются в работе с базой данных. Результаты его заинтересовали. Вместе с тем было принято решение не ограничиваться только этими вариантами доступа к базе данных, а расширить список. Кроме того было принято решение выполнить в качестве теста достаточно типовые задачи, например типичный запрос к регистру остатков для построения отчета.
В данном тестировании будут принимать участие следующие методы доступа к данным:
- стандартный доступ путем выполнения запроса
1С
; - прямой запрос к данным с помощью
rainbow
; - прямой запрос к данным с помощью технологии
ADO
; - прямой запрос к данным с помощью
1С++
; - прямой запрос к данным с помощью внешней компоненты
neta.dll
.
Параметры тестирования:
Будет использоваться самописная база на компоненте «Торговля
». База содержит информацию за период 2,5
года. Перед тестированием базы данных, она была сжата («Shrink Database
»). Объем базы данных, отображаемый в «EM
», составляет 465
мегабайт. Тестовой задачей будет построение стандартного отчета по регистру «Партии
» с периодом с «05.02.2002
» по «20.01.2004
» (то есть почти за два года). В первом варианте будет задано условие построения отчета по двум местам хранения (без группирования по местам хранения, простое суммирование данных). Второй вариант построяния отчета - без всяких условий.
В запросе должна быть единственная группировка по товару. В отчет выводятся начальные и конечные остатки за период, а также приход и расход товаров в количественном выражении. Печатная форма выводится. Замер времени работы будет производиться с помощью недокументированной функции 1С
: _GetPerformanceCounter()
.
Тестовая платформа:
Аппаратная часть: Атлон-1400 512Мб DDR, HDD 2*40Гб IDE Raid-0
.
Программная часть: Win2003 server, SQL 2000 server sp3, 1С (21 релиз)
.
В качестве тестового вопроса будет использоваться стандартный запрос по регистру партий товаров. Посмотреть текст запроса (вариант без фильтра по местам хранения), а заодно и прочитать статью можно здесь. Запрос с фильтром по двум местам хранения не приводится с целью экономии места и времени. Внимание:
отличие тестового запроса от приведенного в том, что результирующая таблица в тестовом запросе еще соединяется со справочником товаров, группируется по наименованию и сортируется по возрастанию. Таким образом запрос возвращает наименование товара, а не ID
объекта.
Код модуля отчета для тестирования представлен ниже, думаю по названиям процедур видно, какая за что отвечает...
Перем НачДата
,КонДата
;
Перем Магазин1
, Магазин2
;
Перем ТекстЗапросаРадуга
, ТекстЗапросаАДО
;
//________________________________________________________
Процедура Выполнить1С
()
НачЧисло
=_GetPerformanceCounter();
//начало теста
Запрос
=СоздатьОбъект("Запрос"
);
СЗ
=СоздатьОбъект("СписокЗначений"
);
СЗ
.ДобавитьЗначение
(Магазин1
);
СЗ
.ДобавитьЗначение
(Магазин2
);
ТекстЗапроса
="//{{ЗАПРОС(ОдинС)
|Период с НачДата по КонДата;
|Магазин = Регистр.Партии.Магазин;
|Наименование = Регистр.Партии.Товар;
|Количество = Регистр.Партии.Количество;
|Функция Нач = НачОст(Количество);
|Функция Прих = Приход(Количество);
|Функция Расх = Расход(Количество);
|Функция Кон = КонОст(Количество);
|Группировка Наименование без групп;
|Условие(Магазин в СЗ);
|"
//}}ЗАПРОС
;
Если Запрос
.Выполнить
(ТекстЗапроса
)=0
Тогда
Предупреждение("Не выполнился запрос!"
,10
);
Возврат;
КонецЕсли;
Таб
=СоздатьОбъект("Таблица"
);
Таб
.ИсходнаяТаблица
("Таблица"
);
Таб
.ВывестиСекцию
("Заголовок"
);
Пока Запрос
.Группировка
(1
)=1
Цикл
ТекИмя
=Запрос
.Наименование
;
ТекНач
=Запрос
.Нач
;
ТекПрих
=Запрос
.Прих
;
ТекРасх
=Запрос
.Расх
;
ТекКон
=Запрос
.Кон
;
Таб
.ВывестиСекцию
("Строка"
);
КонецЦикла;
Таб
.Опции
(0
,0
,0
,0
);
Таб
.ТолькоПросмотр
(1
);
Таб
.Показать
();
Запрос
=""
;
//конец теста
КонЧисло
=_GetPerformanceCounter();
ТекРезультат
=КонЧисло
-НачЧисло
;
Сообщить(ТекРезультат
);
КонецПроцедуры
//________________________________________________________
Процедура ВыполнитьРадуга
()
НачЧисло
=_GetPerformanceCounter();
//начало теста
ЗапросРадуги
=СоздатьОбъект("ODBCQuery"
);
Таб
=СоздатьОбъект("Таблица"
);
Таб
.ИсходнаяТаблица
("Таблица"
);
Таб
.ВывестиСекцию
("Заголовок"
);
Если ЗапросРадуги
.Prepare
(ТекстЗапросаРадуга
,1
,1
)=1
Тогда
Если ЗапросРадуги
.Open
()=1
Тогда
ЗапросРадуги
.GotoNext
();
Пока ЗапросРадуги
.IsOK
()=1
Цикл
//Считываем данные
ТекИмя
=ЗапросРадуги
.GetString
(0
);
ТекНач
=ЗапросРадуги
.GetString
(1
);
ТекПрих
=ЗапросРадуги
.GetString
(2
);
ТекРасх
=ЗапросРадуги
.GetString
(3
);
ТекКон
=ЗапросРадуги
.GetString
(4
);
Таб
.ВывестиСекцию
("Строка"
);
//****************
ЗапросРадуги
.GotoNext
();
КонецЦикла;
ЗапросРадуги
.Close
();
Иначе
Предупреждение("Ошибка открытия запроса!"
,10
);
КонецЕсли;
ЗапросРадуги
.Reset
();
Иначе
Предупреждение("Ошибка выполнения запроса!"
,10
);
КонецЕсли;
ЗапросРадуги
=""
;
Таб
.Опции
(0
,0
,0
,0
);
Таб
.ТолькоПросмотр
(1
);
Таб
.Показать
();
//конец теста
КонЧисло
=_GetPerformanceCounter();
ТекРезультат
=КонЧисло
-НачЧисло
;
Сообщить(ТекРезультат
);
КонецПроцедуры
//________________________________________________________
Процедура ВыполнитьАДО
()
//начало теста
Если МонопольныйРежим()=1
Тогда
Предупреждение("Невозможно выполнить запрос в монопольном режиме!"
,10
);
//Действия
Возврат;
//********
КонецЕсли;
Соединение
=СоздатьОбъект("ADODB.Connection"
);
ТекСервер
="***"
;
ТекПароль
="***"
;
ТекБаза
="***"
;
СтрокаКоннекта
="driver={SQL Server};server="
+ТекСервер
+";uid=sa;pwd="
+ТекПароль
+";Database="
+ТекБаза
;
Соединение
.ConnectionTimeOut
=20
;
Соединение
.CursorLocation
=3
;
Попытка
Соединение
.Open
(СтрокаКоннекта
);
Исключение
Предупреждение("Невозможно установить соединение с базой данных!"
);
//Действия
//********
КонецПопытки;
Таб
=СоздатьОбъект("Таблица"
);
Таб
.ИсходнаяТаблица
("Таблица"
);
Таб
.ВывестиСекцию
("Заголовок"
);
ЗапросАДО
=СоздатьОбъект("ADODB.Command"
);
ЗапросАДО
.ActiveConnection
=Соединение
;
НачЧисло
=_GetPerformanceCounter();
ЗапросАДО
.CommandText
=ТекстЗапросаАДО
;
Выборка
=ЗапросАДО
.Execute
();
Если Выборка
.EOF
()=-1
Тогда
Иначе
Выборка
.MoveFirst
();
Пока Выборка
.EOF
()=0
Цикл
//Обработка выборки данных
ТекИмя
=Выборка
.Fields
(0
).Value
;
ТекНач
=Выборка
.Fields
(1
).Value
;
ТекПрих
=Выборка
.Fields
(2
).Value
;
ТекРасх
=Выборка
.Fields
(3
).Value
;
ТекКон
=Выборка
.Fields
(4
).Value
;
Таб
.ВывестиСекцию
("Строка"
);
//*****************
Выборка
.MoveNext
();
КонецЦикла;
КонецЕсли;
Выборка
.Close
();
Соединение
.Close
();
Выборка
=""
;
ЗапросАДО
=""
;
Соединение
=""
;
Таб
.Опции
(0
,0
,0
,0
);
Таб
.ТолькоПросмотр
(1
);
Таб
.Показать
();
//конец теста
КонЧисло
=_GetPerformanceCounter();
ТекРезультат
=КонЧисло
-НачЧисло
;
Сообщить(ТекРезультат
);
КонецПроцедуры
//________________________________________________________
Процедура Выполнить1Спп
()
НачЧисло
=_GetPerformanceCounter();
//начало теста
Запрос
=СоздатьОбъект("ODBCRecordSet"
);
Запрос
.Открыть
(ТекстЗапросаРадуга
);
Запрос
.УстТипыКолонок1С
("Строка,Число,Число,Число,Число"
);
ТЗ
=СоздатьОбъект("ТаблицаЗначений"
);
Запрос
.ПолучитьРезультатыВ_ТЗ
(ТЗ
,1
);
Запрос
.Закрыть
();
Таб
=СоздатьОбъект("Таблица"
);
Таб
.ИсходнаяТаблица
("Таблица"
);
Таб
.ВывестиСекцию
("Заголовок"
);
ТЗ
.ВыбратьСтроки
();
Пока ТЗ
.ПолучитьСтроку
()=1
Цикл
ТекИмя
=ТЗ
.Имя
;
ТекНач
=ТЗ
.Нач
;
ТекПрих
=ТЗ
.Прих
;
ТекРасх
=ТЗ
.Расх
;
ТекКон
=ТЗ
.Кон
;
Таб
.ВывестиСекцию
("Строка"
);
КонецЦикла;
Таб
.Опции
(0
,0
,0
,0
);
Таб
.ТолькоПросмотр
(1
);
Таб
.Показать
();
//конец теста
КонЧисло
=_GetPerformanceCounter();
ТекРезультат
=КонЧисло
-НачЧисло
;
Сообщить(ТекРезультат
);
КонецПроцедуры
//________________________________________________________
Процедура ВыполнитьНета
()
НачЧисло
=_GetPerformanceCounter();
//начало теста
Запрос
=СоздатьОбъект("AddIn.NPeriodik"
);
Таб
=СоздатьОбъект("Таблица"
);
Таб
.ИсходнаяТаблица
("Таблица"
);
Таб
.ВывестиСекцию
("Заголовок"
);
ТЗ
=СоздатьОбъект("ТаблицаЗначений"
);
ТЗ
.НоваяКолонка
("Имя"
);
ТЗ
.НоваяКолонка
("Нач"
);
ТЗ
.НоваяКолонка
("Прих"
);
ТЗ
.НоваяКолонка
("Расх"
);
ТЗ
.НоваяКолонка
("Кон"
);
ТЗПарм
=СоздатьОбъект("ТаблицаЗначений"
);
ТЗПарм
.НоваяКолонка
("Номер"
);
ТЗПарм
.НоваяКолонка
("Тип"
);
ТЗПарм
.НоваяКолонка
("Размер"
);
ТЗПарм
.НоваяСтрока
();
ТЗПарм
.Номер
=1
;
ТЗПарм
.Тип
=1
;
ТЗПарм
.Размер
=0
;
ТЗПарм
.НоваяСтрока
();
ТЗПарм
.Номер
=2
;
ТЗПарм
.Тип
=4
;
ТЗПарм
.Размер
=4
;
ТЗПарм
.НоваяСтрока
();
ТЗПарм
.Номер
=3
;
ТЗПарм
.Тип
=4
;
ТЗПарм
.Размер
=4
;
ТЗПарм
.НоваяСтрока
();
ТЗПарм
.Номер
=4
;
ТЗПарм
.Тип
=4
;
ТЗПарм
.Размер
=4
;
ТЗПарм
.НоваяСтрока
();
ТЗПарм
.Номер
=5
;
ТЗПарм
.Тип
=4
;
ТЗПарм
.Размер
=4
;
//________________________________________________________
Запрос
.ЗапросК1С
(ТекстЗапросаРадуга
,ТЗПарм
,ТЗ
,2
);
ТЗ
.ВыбратьСтроки
();
Пока ТЗ
.ПолучитьСтроку
()=1
Цикл
ТекИмя
=ТЗ
.Имя
;
ТекНач
=ТЗ
.Нач
;
ТекПрих
=ТЗ
.Прих
;
ТекРасх
=ТЗ
.Расх
;
ТекКон
=ТЗ
.Кон
;
Таб
.ВывестиСекцию
("Строка"
);
КонецЦикла;
Таб
.Опции
(0
,0
,0
,0
);
Таб
.ТолькоПросмотр
(1
);
Таб
.Показать
();
//конец теста
КонЧисло
=_GetPerformanceCounter();
ТекРезультат
=КонЧисло
-НачЧисло
;
Сообщить(ТекРезультат
);
КонецПроцедуры
//________________________________________________________
Процедура ПриОткрытии
()
НачДата
='05.02.2002'
;
КонДата
='20.01.2004'
;
Спр
=СоздатьОбъект("Справочник.Магазины"
);
Спр
.НайтиПоКоду
("1"
);
Магазин1
=Спр
.ТекущийЭлемент
();
Спр
.НайтиПоКоду
("3"
);
Магазин2
=Спр
.ТекущийЭлемент
();
Если ЗагрузитьВнешнююКомпоненту("1cpp.dll"
)=0
тогда
Предупреждение ("Компонента 1с++ не найдена"
);
Форма
.кнПлюс
.Доступность
(0
);
КонецЕсли;
Если ЗагрузитьВнешнююКомпоненту("neta.dll"
)=0
тогда
Предупреждение ("Компонента neta не найдена"
);
Форма
.кнНета
.Доступность
(0
);
КонецЕсли;
КонецПроцедуры
Тестирования проводилось по 3 раза для каждого варианта. Перед началом тестирования все варианты прогонялись, чтобы все необходимые данные закэшировались в оперативной памяти. Время теста дано в миллисекундах.
Ниже приведена таблица для режима тестирования с фильтром по двум местам хранения:
№ | Наименование. | ||||
---|---|---|---|---|---|
1C | Rainbow | ADO | 1С++ | Neta.dll | |
1 | 36298 | 4243 | 4302 | 3810 | 3802 |
2 | 35838 | 4219 | 4238 | 3797 | 3800 |
3 | 36060 | 4203 | 4226 | 3753 | 3798 |
Среднее: | 36065 | 4222 | 4255 | 3787 | 3800 |
Ниже приведена таблица для режима тестирования без фильтров:
№ | Наименование. | ||||
---|---|---|---|---|---|
1C | Rainbow | ADO | 1С++ | Neta.dll | |
1 | 149338 | 6502 | 6429 | 5889 | 6004 |
2 | 155806 | 6389 | 6437 | 5880 | 5998 |
3 | 151409 | 6451 | 6393 | 5889 | 5995 |
Среднее: | 152184 | 6447 | 6420 | 5886 | 5999 |
Итак, объявляем победителей. Первое место достается 1С++
, второе Neta.dll
, третье rainbow
.
Также хотелось бы обратить внимание на некоторые закономерности, выявленные тестированием. При отключении фильтра по местам хранения время формирования прямого запроса увеличилось в 1,5
раза. 1C
при этом стала затрачивать почти в 4,5 раза больше времени. Это показывает нам, что производительность стандартного запроса 1С
деградирует гораздо более высокими темпами при росте выборки, чем производительность прямого запроса.
Начать дискуссию