## Оптимизация клиент-серверных приложений и особенности их разработки для использования в модели сервиса
### Введение
На момент написания этой статьи для работы приложения в модели сервиса требовалась интеграция с 1С:Библиотекой стандартных подсистем (БСП) 8.2. Однако сейчас ведется работа по выделению функционала для разработки приложений в модели сервиса из БСП в отдельную библиотеку — 1С:Библиотека технологий сервиса (БТС). Это позволит ускорить внедрение и обновление необходимых подсистем. Пока эта работа не завершена, в статье будет использоваться термин БСП.
```1C
// Пример интеграции с БСП
ПодключитьБиблиотеку("БиблиотекаСтандартныхПодсистем");
```
### Работа с базой данных
#### Эффективные запросы
1. **Адекватность запроса**: Запрос должен соответствовать решаемой задаче и выбирать только необходимые данные. Избыточные данные увеличивают нагрузку на базу данных, особенно при большом количестве пользователей.
```1C
// Пример эффективного запроса
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Справочник.Наименование,
| Справочник.Код
|ИЗ
| Справочник.Номенклатура КАК Справочник
|ГДЕ
| Справочник.ВидНоменклатуры = &ВидНоменклатуры";
```
2. **Избегайте сложных запросов**:
- Избегайте чрезмерного количества соединений (например, 15 и более), операций выбора и выражений ИЛИ, так как это усложняет понимание запроса СУБД и может привести к предупреждению timeout warning.
```1C
// Пример сложного запроса (не рекомендуется)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ...
|ИЗ
| Таблица1 КАК Т1
| ЛЕВОЕ СОЕДИНЕНИЕ Таблица2 КАК Т2 ПО Т1.Поле = Т2.Поле
| ЛЕВОЕ СОЕДИНЕНИЕ Таблица3 КАК Т3 ПО Т2.Поле = Т3.Поле
| ...
|ГДЕ
| Т1.Поле1 = &Значение1
| ИЛИ Т2.Поле2 = &Значение2";
```
- Вложенные запросы также могут усложнить понимание запроса СУБД, поэтому их следует использовать только при явной необходимости.
```1C
// Пример вложенного запроса (использовать с осторожностью)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т1.Поле1,
| (ВЫБРАТЬ Т2.Поле2 ИЗ Таблица2 КАК Т2 ГДЕ Т2.Поле = Т1.Поле) КАК Поле2
|ИЗ
| Таблица1 КАК Т1";
```
3. **Простые условия соединения**: Условия соединения должны быть простыми и использовать поля, по которым есть индексы, для обеспечения эффективности.
```1C
// Пример простого условия соединения с использованием индексированного поля
Запрос.Текст =
"ВЫБРАТЬ
| Т1.Поле1,
| Т2.Поле2
|ИЗ
| Таблица1 КАК Т1
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Таблица2 КАК Т2 ПО Т1.ИндексированноеПоле = Т2.ИндексированноеПоле";
```
4. **Количество таблиц**:
- Чем больше таблиц участвует в запросе, тем сложнее СУБД построить эффективный план запроса.
- Сокращение количества таблиц — один из способов оптимизации неэффективных запросов.
```1C
// Пример запроса с минимальным количеством таблиц
Запрос.Текст =
"ВЫБРАТЬ
| Т1.Поле1,
| Т1.Поле2
|ИЗ
| Таблица1 КАК Т1";
```
#### Эффективные планы запросов
1. **План запроса**: СУБД самостоятельно определяет, как выполнить запрос, и строит план запроса, который может быть как эффективным, так и нет.
2. **Влияние на план запроса**:
- Хотя нет возможности явно указать СУБД, какой план использовать, можно косвенно влиять на него, используя:
- Текст запроса
- Существующие индексы
- Актуальную статистику СУБД
3. **Актуальность данных**: План запроса может меняться в зависимости от количества данных и актуальности статистики СУБД. Поэтому важно тестировать запросы на реальных данных и актуальной статистике.
#### Индексы
1. **Назначение индексов**: Индексы ускоряют выборку данных из таблицы. 1С:Предприятие автоматически создает индексы, но разработчик может влиять на них, устанавливая признак индексирования для реквизитов.
```1C
// Пример установки индексирования для реквизита
РеквизитОбъекта.Индексирование = Истина;
```
2. **Эффективность индексов**:
- Индексы, добавленные разработчиками, часто неэффективны, так как создаются без учета реальных условий использования.
- Чрезмерное количество индексов может ухудшить производительность, так как СУБД будет тратить больше времени на выбор подходящего индекса.
3. **Использование индексов**:
- Индекс будет использоваться, если поля, по которым происходит выборка, находятся в вершине индекса.
```1C
// Пример использования индекса
Запрос.Текст =
"ВЫБРАТЬ
| Т1.Поле1
|ИЗ
| Таблица1 КАК Т1
|ГДЕ
| Т1.ИндексированноеПоле = &Значение";
```
- Если подходящего индекса нет, СУБД будет использовать сканирование индекса или таблицы, что может быть неэффективно при большом объеме данных.
4. **Селективность индексов**:
- Селективность индекса обратно пропорциональна количеству записей, которые можно выбрать с его помощью.
- Индексы по неселективным полям (например, по полю типа Булево) неэффективны, так как не позволяют значительно сузить выборку.
```1C
// Пример неэффективного индекса (поле типа Булево)
РеквизитОбъекта.Индексирование = Истина; // Неэффективно для полей типа Булево
```
#### Транзакции
1. **Назначение транзакций**: Транзакции обеспечивают атомарность, согласованность, изолированность и долговечность операций с данными.
2. **Явное управление транзакциями**:
- В большинстве случаев транзакции открываются автоматически платформой.
```1C
// Пример явного управления транзакциями
НачатьТранзакцию();
Попытка
// Операции с данными
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
```
- При необходимости можно открывать транзакции явно с помощью методов НачатьТранзакцию(), ЗафиксироватьТранзакцию() и ОтменитьТранзакцию().
- Вложенные транзакции невозможны; при открытии новой транзакции увеличивается внутренний счетчик, но фактически транзакция остается одна.
3. **Управляемые блокировки**:
- Блокировки используются для предотвращения конфликтов при одновременном доступе к данным.
```1C
// Пример установки управляемой блокировки
БлокировкаДанных = Новый БлокировкаДанных;
ЭлементБлокировки = БлокировкаДанных.Добавить("РегистрСведений.СчетаФактуры");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
БлокировкаДанных.Заблокировать();
```
- Не следует блокировать данные, которые не изменяются или не требуют согласованного состояния.
- При изменении данных следует использовать исключительные блокировки, чтобы избежать взаимоблокировок (deadlocks).
4. **Изменения в 8.3**:
- В версии 8.3 при отключении режима совместимости "грязного чтения" разделяемые блокировки можно устанавливать только при многократном чтении данных в рамках одной транзакции.
5. **Длительные транзакции**:
- Длительные транзакции нежелательны, так как увеличивают нагрузку на СУБД и могут привести к исчерпанию ресурсов.
- Рекомендуется разбивать длинные транзакции на более короткие.
#### Использование динамических списков
1. **Режимы работы**:
- Динамические списки могут работать в режиме динамического считывания данных, что является наиболее эффективным.
```1C
// Пример настройки динамического списка
ДинамическийСписок = Элементы.ДинамическийСписок;
ДинамическийСписок.ОсновнаяТаблица = "Справочник.Номенклатура";
ДинамическийСписок.ДинамическоеСчитываниеДанных = Истина;
```
- При отсутствии динамического считывания данные считываются порциями и хранятся в буфере на сервере.
- В отсутствие основной таблицы список считывает данные целиком, что неэффективно.
2. **Оптимизация запросов**:
- Запросы динамических списков должны быть простыми, чтобы обеспечить эффективное использование индексов.
```1C
// Пример запроса для динамического списка
ДинамическийСписок.ТекстЗапроса =
"ВЫБРАТЬ
| Справочник.Наименование,
| Справочник.Код
|ИЗ
| Справочник.Номенклатура КАК Справочник
|ГДЕ
| Справочник.ВидНоменклатуры = &ВидНоменклатуры";
```
- Для полей, по которым происходит сортировка, следует включать индексирование с дополнительным упорядочиванием.
```1C
// Пример установки индексирования с дополнительным упорядочиванием
РеквизитОбъекта.Индексирование = Истина;
РеквизитОбъекта.ДополнительноеПорядок = "Возр";
```
#### Поддержка различных СУБД
1. **Тестирование**:
- Приложение должно быть протестировано на всех поддерживаемых СУБД, чтобы обеспечить одинаковую эффективность работы.
```1C
// Пример проверки работы на разных СУБД
// Функция проверки работы на разных СУБД
Функция ПроверитьРаботуСУБД()
Если БазаДанных = "Файловая" Тогда
// Проверка для файловой СУБД
ИначеЕсли БазаДанных = "Microsoft SQL Server" Тогда
// Проверка для Microsoft SQL Server
ИначеЕсли БазаДанных = "IBM DB2" Тогда
// Проверка для IBM DB2
ИначеЕсли БазаДанных = "PostgreSQL" Тогда
// Проверка для PostgreSQL
ИначеЕсли БазаДанных = "Oracle Database" Тогда
// Проверка для Oracle Database
КонецЕсли;
КонецФункции;
```
- В идеале тестирование следует проводить на всех СУБД, но на практике можно ограничиться файловой версией и одной-двумя сторонними СУБД.
#### Поддержка веб-клиента
1. **Использование веб-клиента**:
- Приложение должно быть полностью работоспособно в веб-клиенте, если оно предназначено для использования в сервисе.
```1C
// Пример использования веб-клиента
// В процессе разработки и отладки используется веб-клиент
```
- Рекомендуется использовать веб-клиента в процессе разработки и отладки для обеспечения совместимости.
2. **Расширение для работы с файлами**:
- Использование расширения для работы с файлами должно быть опциональным, а не обязательным, чтобы не ограничивать функциональность для пользователей без расширения.
```1C
// Пример использования расширения для работы с файлами
// Проверка наличия расширения
Если ЕстьРасширениеДляРаботыСФайлами() Тогда
// Использование расширения
Иначе
// Альтернативный вариант
КонецЕсли;
```
#### Производительность интерфейса
1. **Максимальное время отклика**:
- Рекомендуется стремиться к тому, чтобы максимальное время отклика на одно интерактивное действие пользователя не превышало 1 секунду.
```1C
// Пример измерения времени отклика
// Измерение времени отклика с помощью секундомера
```
2. **Измерение времени отклика**:
- Для измерения времени отклика следует использовать секундомер, а не показатели производительности или дополнительный код на встроенном языке.
```1C
// Пример измерения времени отклика с помощью секундомера
// (невозможно реализовать в коде)
```
- Измерения следует проводить на компьютерах, аналогичных тем, которые используют пользователи, а не на компьютерах для разработки.
3. **Факторы, влияющие на производительность**:
- **Количество клиент-серверных вызовов**: Их количество следует минимизировать, объединяя несколько вызовов в один.
```1C
// Пример минимизации клиент-серверных вызовов
// Объединение нескольких вызовов в один
ОбъединенныйВызов = Новый Массив;
ОбъединенныйВызов.Добавить("Вызов1");
ОбъединенныйВызов.Добавить("Вызов2");
ВыполнитьВызовы(ОбъединенныйВызов);
```
- **Программное изменение формы**: Изменение формы программным способом может снизить производительность из-за особенностей кэширования.
```1C
// Пример программного изменения формы (использовать с осторожностью)
Форма.Элементы.Поле1.Видимость = Ложь;
```
- **Сложные формы**: Формы с большим количеством элементов и условным оформлением работают медленнее.
```1C
// Пример создания сложной формы (не рекомендуется)
Форма.Элементы.Добавить("Закладка1", Тип("ГруппаФормы"));
Форма.Элементы.Добавить("Закладка2", Тип("ГруппаФормы"));
...
```
- **Передача большого объема данных**: Передача данных, которые не всегда нужны, может снизить производительность, особенно при низкой скорости соединения.
```1C
// Пример передачи большого объема данных (не рекомендуется)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ...
|ИЗ
| Таблица1 КАК Т1";
```
- **Использование ключевого слова Знач**:
- При объявлении параметров серверных процедур следует использовать Знач, если возвращаемое значение не нужно на клиенте, чтобы избежать передачи данных обратно.
```1C
// Пример использования ключевого слова Знач
Процедура СервернаяПроцедура(Знач Параметр1)
// Код процедуры
КонецПроцедуры;
```
- Для параметров типа Булево можно не использовать Знач, так как они передаются эффективно.
#### Длительные операции
1. **Механизм длительных операций**:
- Для выполнения длительных операций рекомендуется использовать механизм длительных операций БСП, который позволяет избежать "зависания" клиента.
```1C
// Пример использования механизма длительных операций
// Запуск фонового задания
ФоновоеЗадание = ФоновыеЗадания.СоздатьОбъект();
ФоновоеЗадание.Наименование = "ДлительнаяОперация";
ФоновоеЗадание.НачатьЗадание(ЭтотОбъект, "ВыполнитьДлительнуюОперацию");
```
- Функционал выполняется в фоновом задании, а клиент подключает обработчик ожидания для проверки завершения операции.
#### Бережное использование ресурсов
1. **Оперативная память**:
- Оперативная память является ценным ресурсом, особенно на сервере.
```1C
// Пример экономии оперативной памяти
// Использование курсорных выборок
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка данных
КонецЦикла;
```
- Необходимо избегать неэффективного использования памяти, например, формирования больших структур данных в памяти.
- Для работы с большими файлами следует использовать последовательные методы чтения и записи, такие как ЧтениеXML, ЧтениеТекста, ЗаписьXML, ЗаписьТекста.
```1C
// Пример использования последовательных методов чтения и записи
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл("C:\Файл.xml");
Пока ЧтениеXML.Прочитать() Цикл
// Обработка данных
КонецЦикла;
ЧтениеXML.Закрыть();
```
2. **Утечки памяти**:
- Утечки памяти возникают из-за циклических ссылок и сложны в диагностике.
```1C
// Пример создания циклической ссылки (не рекомендуется)
Объект1 = Новый Объект1;
Объект2 = Новый Объект2;
Объект1.СсылкаНаОбъект2 = Объект2;
Объект2.СсылкаНаОбъект1 = Объект1;
```
- Следует избегать создания циклических ссылок и обращать внимание на структуры данных, которые могут их создавать.
3. **Повторное использование возвращаемых значений**:
- Модули с повторным использованием возвращаемых значений следует использовать только для данных, которые часто используются и не изменяются.
```1C
// Пример использования модуля с повторным использованием возвращаемых значений
// (не рекомендуется для строковых констант)
ОбщийМодуль.ПолучитьСтроку("Константа");
```
- Возвращаемые значения должны быть неизменяемыми, чтобы избежать ошибок.
4. **Работа с временными файлами**:
- Для создания временных файлов следует использовать функцию ПолучитьИмяВременногоФайла().
```1C
// Пример создания временного файла
ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
```
- Разработчик должен самостоятельно удалять временные файлы после использования, так как платформа не всегда может это сделать.
5. **Использование неразделённых данных**:
- Использование неразделённых данных может повысить эффективность, но увеличивает сложность обслуживания.
```1C
// Пример использования неразделённых данных
// (необходимо соблюдать осторожность)
```
- Неразделённые данные следует использовать только для данных, которые должны быть общими для всех пользователей.
- Данные, введенные пользователем, не должны храниться в неразделённых данных.
6. **Оптимизация кода на встроенном языке**:
- Необходимо избегать неоптимальностей в коде, которые могут привести к значительным задержкам при многократном выполнении.
```1C
// Пример оптимизации кода
// Предварительное вычисление значения в цикле
Значение = ВычисляемоеЗначение();
Для i = 1 По 10000 Цикл
// Использование значения
КонецЦикла;
```
7. **Минимизация времени обновления версии информационной базы**:
- Обработчики обновления должны быть оптимизированы, чтобы сократить время простоя базы данных.
```1C
// Пример оптимизации обработчиков обновления
// Использование неразделенных обработчиков обновления
Процедура Обновление_1_0_0_1()
// Код обработчика обновления
КонецПроцедуры;
```
- Рек