Сравнение 5 различных методов доступа к базе данных 1С   

Не так давно на этом сайте были размещены результаты тестирования вариантов доступа к данным на примере Rainbow и технологии ADO. Тестирование проводилось на достаточно сложных запросах, которые нечасто встречаются в работе с базой данных. Результаты его (которые можно посмотреть здесь) заинтересовали. Вместе с тем было принято решение не ограничиваться только этими вариантами доступа к базе данных, а расширить список. Кроме того было принято решение выполнить в качестве теста достаточно типовые задачи, например типичный запрос к регистру остатков для построения отчета.

В данном тестировании будут принимать участие следующие методы доступа к данным:

  • стандартный доступ путем выполнения запроса 1С;
  • прямой запрос к данным с помощью Rainbow;
  • прямой запрос к данным с помощью технологии ADO;
  • прямой запрос к данным с помощью 1C++;
  • прямой запрос к данным с помощью внешней компоненты 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

Итак, объявляем победителей. Первое место достается 1C++, второе Neta.dll, третье Rainbow.

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

 


 

Перепечатка, воспроизведение в любой форме, распространение, в том числе в переводе, любых материалов с сайта www.softpoint.ru возможны только с письменного разрешения компании "СофтПоинт". Это правило действует для всех без исключения случаев, кроме тех, когда в материале прямо указано разрешение на копирование (основание: Закон Российской Федерации "Об авторском праве и смежных правах").