Создаем свои индексы для баз 1С. Со своей структурой и настройками!
Говорим о секционировании таблиц и индексов для баз 1С. Способы применения, подводные камни и прочее.
Внимание! Раньше эта статья была на Инфостарт!
Ранее эта статья была на Инфостарт, но фирма “1С” запретила ее публикацию из-за нарушения лицензионного соглашения, согласно которому прямая работа с базой данных вендором запрещена. Компания Инфостарт подчиняется фирме “1С”, поэтому материал был удален.
Цитата: “Статья описывает недокументированные средства доступа к базе данных 1С и допускает модификацию данных средствами SQL, что может изменить поведение платформы, привести к разрушению базы, скомпрометировать данные, а также привести к отказу в официальной поддержке фирмы «1С».”
Поэтому мой аккаунт на Инфостарт теперь полностью удален и никакого отношения к материалам на этом сайте я больше не имею, в том числе и тем, которые остались опубликованы от другой учетной записи.
Желаю светлого будущего сообществу разработчиков 1С. Сообществу, где запрещен обмен опытом и любые другие материалы, которые не нравятся монополисту-вендору. Члены сообщества как угодно могут относиться к происходящему. Могу лишь пожелать членам сообщества, разработчикам не обманывать самих себя и не тешиться словами “community” и программы поддержки разработчиков.
О чем речь
Это всего лишь еще одна статья об избитой теме неплатформыенных индексов в информационных базах платформы 1С. Мы поговорим о “плохих” практиках тюнинга, которые с одной стороны запрещены лицензионным соглашением фирмы 1С, а с другой являются наиболее эффективным средством оптимизации производительности запросов. Эдакий запретный плод!
Не рекомендуется к прочтению, если к Вам относится хотя бы один из следующих пунктов:
- Используется файловый режим работы информационной базы
- Нет никаких проблем производительности и стабильности информационной системы
- Считаете большой ошибкой выход за пределы экосистемы платформы 1С
- Вы сотрудник фирмы “1С”
Все, что Вы прочитаете далее, должно остаться внутри Вашей головы и никогда не переходить в практическую плоскость. Еще раз повторяю: все что сказано далее — это плохие практики, нерекомендуемые фирмой “1С” и нарушающие ее лицензионное соглашение, противоречащие материалам подготовки к экзамену “1С:Эксперт по технологическим вопросам”, снижающие карму и просто имеющие подводные камни планетарного масштаба.
Будете читать дальше? Тогда отлично, поехали!
Зачем все это
Платформа 1С создает множество индексов самостоятельно по настройкам объектов метаданных конфигурации. Также разработчики сами могут в ограниченном режиме влиять на создание и изменение индексов. Самую полную информацию о платформенных индексах смотрите здесь.
Посмотрели? Задаетесь вопросом почему нужны еще какие-то дополнительные индексы?
Ответ на этот вопрос простой. Он даже проще, чем можно себе представить!
Индексы нужны для повышения эффективности запросов на чтение при поиске данных. При этом вариантов выборки данных, фильтров, группировок и порядка сортировки может быть огромное количество. Чем больше полей в таблице, тем больше этих вариантов. Даже чисто гипотетически трудно себе представить ситуацию, когда платформенные индексы могли бы полностью удовлетворить большую часть запросов. Частично — может быть, полностью — никогда.
Ситуация усугубляется еще и тем, что добавление индексов через свойство полей “Индексировать” в конфигураторе имеет примитивные возможности для их настройки. Фактически, Вы можете только выбрать три варианта (да и то не всегда):
- Не индексировать (с этим и так все понятно).
- Индексировать (индекс по выбранному полю + доп. поля для уникальности комбинации значений).
- Индексировать с дополнительным упорядочиванием (индекс по выбранному полю + поля упорядочивания, по которым обычно сортируют данные этой таблицы).
Повторюсь, что примеры того, как влияют эти настройки на индексы можно посмотреть здесь. Почему же этих вариантов недостаточно?
Вот Вам пару вопросов для обдумывания:
- А что если нужно создать индекс сразу по нескольким реквизитам?
- А если нужен индекс по пометке удаления?
- Как построить эффективный индекс по любому полю с типом “Булево”?
- Как добавить индексы в служебные таблицы, недоступные непосредственно в метаданных конфигурации (некоторые основные таблицы, таблицы итогов и др.?
- Можно ли сделать покрывающий индекс, чтобы исключить операции Lookup к основной таблице или кластерному индексу?
Таких вопросов можно задавать много. Вы говорите, что все это можно решить средствами платформы? Или что это все не нужно и разработчики конфигурации просто ошиблись в архитектуре. Тогда прошу напишите в комментариях Ваши решения, но у меня будет лишь пару вопросов:
- Почему бы не использовать произвольные индексы на уровне базы данных с произвольной структурой и настройками?
- Для чего тогда были придуманы фильтрованные индексы, покрывающие индексы, произвольные составные индексы, если платформа 1С их не использует? От лукавого? 🙂
А можно примеры?
Конечно, для наглядности рассмотрим в качестве примеров ответы на те самые вопросы, которые я написал выше. И так, погнали!
А что если нужно создать индекс сразу по нескольким реквизитам
Предположим, что у нас есть справочник “ФизическиеЛица” следующей структуры (некоторые поля пропущены). В самой таблице примерно 2 млн. записей.
Поле 1С | Имя SQL | Тип SQL |
---|---|---|
Ссылка | _IDRRef | binary(16) |
ПометкаУдаления | _Marked | binary(1) |
Код | _Code | nvarchar(10) |
Наименование | _Description | nvarchar(50) |
ДатаРождения | _Fld11066 | datetime2(0) |
Фамилия | _Fld105061 | nvarchar(50) |
Имя | _Fld105062 | nvarchar(50) |
Отчество | _Fld105063 | nvarchar(50) |
ДатаСоздания | _Fld115447 | datetime2(0) |
РазделительДанных | _Fld1551 | numeric(7,0) |
В конфигурации есть запрос поиска физического лица по комбинации полей Фамилия + Имя + Отчество + ДатаРождения.
Чтобы ускорить поиск и исключить операции полного сканирования таблицы справочника необходимо создать индекс. Какой бы индекс Вы создали средствами платформы?
Конечно, можно поставить свойство “Индексирование” в “Индексировать” для каждого реквизита, но что это даст? СУБД сможет использовать один индекс в каждой конкретной операции плана запроса, при этом также будет учитываться актуальность статистики. Запрос 1С конвертируется в такой SQL-запрос.
Взгляните на план его выполнения ниже (некоторые части разбиты на несколько строк для удобства чтения).
Описание основных действий при выполнении плана запроса следующие.
Порядок | Операция | Количество прочитанных строк | Описание |
---|---|---|---|
1. | Index Seek | 94 | Поиск по индексу “_Reference477_ByFieldFld11066” для реквизита “ДатаРождения”. Повезло, для выбранной даты рождения всего 94 значения, хорошая селективность. |
2. | Index Seek | 10006 | Поиск по индексу “_Reference477_ByFieldFld105062” для поля “Имя”. Тут все намного хуже, т.к селективность у этого поля ниже, ведь имя у многих физических лиц может совпадать. |
3. | Merge Join | 2 | Объединение результатов 1 и 2 операции методом слияния. В результате получаем две фактически одинаковые строки. |
4. | Clustered Index Seek | 1 | Выполняется операция Key Lookup для получения полей запроса, которые не были получены в предыдущих операциях. Key Lookup как известно всегда достаточно тяжелая операция. |
5. | Nested Loops | 1 | Вложенным циклом соединяются результаты 3 и 4 операции и получаем итоговый результат запроса — всего 1 строку. |
- 419 логических чтений.
- 11 миллисекунд времени выполнения.
- 16 миллисекунд CPU.
- Выборка некоторых частей плана запроса отбирает 10000 записей, но в итоговый результат отбирается всего 1.
Вот вам и индексы. 4 индекса, а эффективности никакой. Но нас ничто не остановит, мы создадим свой индекс с произвольными полями и нужной структурой!
Выполним запрос поиска еще раз и вот результат!
План запроса стал значительно проще.
Порядок | Операция | Количество прочитанных строк | Описание |
---|---|---|---|
1. | Index Seek | 1 | Операция поиска в некластеризованном индексе “_ByNameAndBirthday”. Поскольку индекс содержит все необходимые поля выборки запроса, то обращение к кластерному индексу отсутствует, т.е. индекс является покрывающим. |
- 337 логических чтений
- 2 миллисекунд времени выполнения
- 0 миллисекунд CPU (фактически значение незначительное, поэтому не было отловлено трассировкой)
- План выполнения содержит только одну операцию “Index Seek”, которая фактически читает только 1 строку.
Пусть Вас не смущает, что запрос и в начальном варианте выполнялся быстро, ведь это только простой пример. Представьте рабочее окружение, тысячи или десятки тысяч запросов. Тогда разница даже на таких часто выполняемых запросах будет заметна!
Результаты говорят сами за себя. Создать подобный индекс средствами платформы 1С просто нет возможности. Но теперь Вы знаете, какой огромный потенциал для оптимизации у Вас есть.
А если нужен индекс по пометке удаления
Еще один наглядный пример — это индекс по пометке удаления. Средствами платформы добавить индекс по пометке удаления нет возможности, т.к. это стандартный реквизит и настройка “Индексирование” для него просто недоступна. Немного приблизим задачу к настоящей и скажем, что нужно отбирать помеченные на удаление элементы с учетом реквизита “ДатаСоздания”.
И так, платформа не даст создать такой индекс, но мы то знаем решение!
Что это за условие “WHERE”? SQL Server поддерживает фильтрованные индексы, в которых можно ограничить какие данные в него попадут. Если нужна более подробная информация, то Welcome! Самое главное, что нужно знать — фильтрованные индексы улучшают производительность, качество плана выполнения, расходы на обслуживание и хранение. Очень жаль, что платформа 1С не использует такие возможности СУБД.
Проверим новый индекс запросом.
И вот план запроса.
То что надо! Подробно, как в прошлый раз, описывать план запроса не будем, но вот что стоит заметить: единственная значимая операция здесь — это “Index Seek”, которая как-раз и использует наш новый индекс “_ByDeletionMarkAndCreationDate”. Никаких обращений к основной таблице / кластерному индексу не выполнялось, то есть индекс полностью удовлетворяет условиям и полям выборки запроса, является покрывающим.
- Время выполнения 7 миллисекунд
- Количество логических чтений = 459
- Время затраченное CPU 16 миллисекунд
- План запроса простейший, самая значимая часть — это поиск по индексу “_ByDeletionMarkAndCreationDate”
- Количество прочитанных строк — 3 (столько помеченных на удаление элементов за указанный период)
Для интереса создадим такой же индекс, но без фильтра по пометке удаления. Т.к. пример слишком простой, то разницы в результатах выполнения запроса мы не увидим, но размеры индексов будут разительно отличаться:
- фильтрованный индекс занимает 1 страницу и включает в себя 3 строки конечного уровня.
- полный индекс занимает 10319 страниц и содержит 2451400 строк конечного уровня.
А если не видно разницы, то зачем платить больше? 🙂
Таким же способом можно добавлять индексы для любых полей с типом “Булево” и это всегда будет эффективнее, чем добавлять индексы платформенными средствами.
Можно ли сделать покрывающий индекс
Конечно, и мы это уже сделали в двух предыдущих примерах, где созданные некластерные индексы полностью удовлетворяли условиям и выбираемым полям в запросе.
Покрывающий индекс — это такой индекс, который полностью удовлетворяет условиям запроса. В таких случаях в планах запроса будут отсутствовать операции типа Key Lookup, “добирающие” необходимые поля из таблицы или кластерного индекса (если у таблицы нет кластерного индекса, то операция называется RID Lookup).
Можно лишь добавить, что злоупотреблять покрывающими индексами не стоит и нужно хорошо подумать, прежде чем их создавать. Но это относится вообще ко всем индексам, подходите к ним с умом. Покрывающие индексы могут создаваться либо включением необходимых полей непосредственно в индексируемые поля, либо в покрывающие поля (INCLUDE). Например, можно создать индекс по ФИО + ДатеРождения, но дополнить его полями “Наименование” и “Код”. О преимуществах индекса с включенными полями можно ознакомиться здесь, но главное — это возможность значительно повысить производительность за счет включения в индекс всех необходимых для запроса полей без учета ограничения длины ключа и типов данных.
Теперь, если выполнить запрос из предыдущего примера, но с выбором полей “Наименование” и “Код”, то новый индекс позволит выполнить его максимально эффективно.
А теперь представьте какие возможности бы у нас были, если бы такие индексы можно было настраивать через метаданные конфигурации.
Как добавить индексы в служебные таблицы Рассмотрим еще один пример, когда неплатформенные индексы являются единственным эффективным решением. Оговорюсь — можно будет не создавать в этом случае индекс, а делать костыли в виде дополнительных объектов метаданных, которые обрастут различными обработчиками, проверками и т.д., но это явно не лучший путь.
Итак, у нас есть регистр бухгалтерии “Хозрасчетный”. Думаю, что все с ним знакомы. В конфигурации есть запросы к физической таблице регистра нескольких видов.
А также вот такой запрос.
Индексов в основной таблице регистра бухгалтерии, которые бы удовлетворяли этим запросом, просто нет. Частично запросы покрываются индексом по счету ДТ и индексом по счету КТ, но с некоторыми оговорками:
- Необходимо обязательно указать фильтр по организации, чтобы фильтр по периоду работал эффективно.
- Платформенные индексы не учитывают флаг активности проводок, т.к. судя по всему изначально задумывалось, что большинство данных будет получаться из виртуальных таблиц (но это не точно 🙂
- Нет ни одного покрывающего индекса, который бы полностью удовлетворял запросам в т.ч. и по выбираемым полям.
На индексы физической таблицы регистров бухгалтерии можно влиять в ограниченном режиме с помощью настроек метаданных. Но в любом случае, включать в индексы такие поля как “Активность”, “СчетДТ”, “СчетКТ” не получится. В этом случае можно создать два неплатформенных индекса.
Подробнее на этом примере останавливаться не будем, главное было показать, что таким подходом можно оптимизировать запросы к любым таблицам, в т.ч. и служебным, которые скрыты от разработчиков 1С: таблицы итогов, некоторые части физических таблиц и др.
Например, в одной из версий платформы были проблемы с таблицами итогов среза первых и среза последних для регистров сведений. Суть проблемы была в отсутствии кластерного индекса для этих таблиц, в результате чего запросы к ним выполнялись не самым оптимальным способом. Но с помощью “магии” неплатформенных индексов эту ситуацию можно было бы быстро исправить, а так пришлось бы ждать версии 8.3.13, в которой эта проблема была решена (см. раздел “Оптимизации”).
А в чем подвох?
Самый главных подвох, подводный камень и просто неприятность — при реструктуризации базы данных средствами платформы 1С все те индексы, что Вы создадите скриптами самостоятельно — будут удалены. Вам потребуется заново запустить эти скрипты после реструктуризации.
Тут сразу стоит оговориться, что удалены они будут только у тех таблиц, которые непосредственно и подверглись реструктуризации. Но этот факт не особо успокаивает, ведь всегда есть вероятность того, что при развертывании пакета обновления для конфигурации разработчик / администратор забудет запустить этот скрипт и на следующий день программа будет работать не как ожидалось. Ну, если это было обновление от поставщика конфигурации, то конечно всегда можно выкрутиться, что это дело именно в обновлении и в следующем релизе все поправят :).
Не стоит забывать и про еще один подводный камень — это нарушение лицензионного соглашения фирмы “1С”, а именно 65 пункта, в котором явно сказано, что использовать недокументированные возможности нельзя ни при каких обстоятельствах, даже если сильно хочется. Думайте сами — решайте сами.
На вопрос с лицензионным соглашением техническими средствами мы повлиять никак не сможем, но вот проблему сохранения и поддержки наших собственных индексов решить все таки можно. Как? Один из вариантов смотрите ниже.
Идем своим путем
В далеком 2014 году на глаза попала статья от Brent Ozar про костыльный подход создания индексов с помощью DDL-триггеров. Смысл его был в том, что при создании таблицы запускался наш произвольный скрипт, который бы и добавлял нужные индексы. Это действительно “особый” подход, только перейдите на эту страницу и посмотрите на изображение 🙂
Даже если и приходилось в то время создавать неплатформенные индексы, то заморачиваться с триггерами для их поддержки не приходилось. Достаточно было запускать нужные скрипты Job’ами в ночное время и проблема решалась.
Спустя пару лет встретился с еще одним материалом, на этот раз в центре сообщества разработчиков 1С — на Инфостарте. Алексей Бочков описывал подход по использованию сжатия таблиц и индексов средствами SQL Server, а в качестве инструмента сохранения сжатия при реструктуризации предлагал использовать тот же подход — через DDL-триггеры. Понял, что тема актуальна и используется многими. Особенно порадовал комментарий от Михаила Максимова.
В той или иной степени несколько лет использовал DDL-триггеры для разных баз с целью упростить сопровождение индексов (создание новых и изменение платформенных), файловых групп, сжатия, сегментирования, логирования изменений БД и др. задач. Но когда различных произвольных скриптов стало слишком много, то частично решил задачу сопровождения следующим образом:
- Создал служебную базу с настройками сжатия и произвольными правилами обработки событий создания таблиц и индексов в виде скриптов.
- Добавил скрипты предоставления прав для служебных баз.
- Создал глобальные триггеры обработки этих событий.
- Для того, чтобы снизить риски ошибок скриптов в произвольных правилах, все неплатформенные индексы и другие произвольные действия выполняются после включения следующей настройки:
Инструкция XACT_ABORT указывает, выполняет ли SQL Server автоматический откат текущей транзакции, если инструкция языка Transact-SQL вызывает ошибку выполнения. В моем случае, если такая ошибка происходит, то реструктуризация продолжается в штатном режиме, а информация об ошибке записывается в таблицу логов для последующего разбора проблемы. Пример использования этой инструкции ниже.
Таким образом, удается поддерживать произвольные настройки баз данных платформы 1С, не нарушая работу штатных механизмов. Конечно, там не так радужно, но за несколько лет использования критических ошибок не было обнаружено, все живы и здоровы. Проблемы в основном возникают, если такими способами начинаем поддерживать файловые группы или сегментирование, там нужно учитывать некоторые нюансы.
Посмотреть примеры использования DDL-триггеров для поддержки индексов и других настроек можно здесь. Если найдете проблему или будут вопросы — создавайте Issue, постараюсь ответить. По ссылке описание как создать служебную базу и триггеры, а также начать ее использование.
Вместо заключения
Все, что написано выше, не является правильным подходом при разработке и поддержке баз данных. Но иногда просто нет выхода и приходится искать альтернативные пути решения проблем производительности и стабильности. То, что в мире больших баз считается нормой, для баз 1С применять очень сложно. Вспомните хотя бы сегментирование или простое использование файловых групп. Для 1С — это боль.
Цель всего этого донести, что имеется огромный потенциал для улучшения производительности и обслуживания баз данных 1С, но чтобы его использовать сейчас требуется некоторая хитрость и смекалка. Я очень надеюсь, что в одной из версий платформы 1С появится возможность использовать больше настроек баз данных для индексов, сегментирования, файловых групп и т.д., и в один прекрасный момент такие костыли станут уже больше не нужны.
Зачем разработчику 1С «индексировать» измерения регистров и реквизиты ?
Краткий ответ на вопрос заголовка заключается в том, что это позволит выполнять запросы быстро и уменьшать негативное влияние блокировок на производительность в многопользовательском режиме.
Что такое индекс?
Подобно содержанию в книге, индекс в базе данных позволяет быстро искать конкретные сведения в таблице.
Сначала поговорим про индексы в MS SQL Server.
Индексы представляют собой структуру, позволяющую выполнять ускоренный доступ к строкам таблицы на основе значений одного или более ее столбцов.
Индекс содержит ключи, построенные из одного или нескольких столбцов таблицы или представления, и указатели, которые сопоставляются с местом хранения заданных данных.
Индексы сокращают объем данных, которые необходимо считать, чтобы возвратить результирующий набор.
Хотя индекс и связан с конкретным столбцом (или столбцами) таблицы, все же он является самостоятельным объектом базы данных.
Просто объекта «Индекс» в платформе 1С:Предприятие 8 нет.
Индексы таблиц в базе данных 1С:Предприятие создаются неявным образом при создании объектов конфигурации, а также при тех или иных настройках объектов конфигурации.
- Неявным образом индексы создаются с учетом типов полей ключа данных — набора полей, однозначно определяющих данные. Для объектных типов данных (Справочник, Документ, ПланСчетов и др.) — это «Ссылка»; для регистров, подчиненных регистратору (РегистрНакопления, РегистрБухгалтерии, РегистрСведений, подчиненный регистратору и др.) — «Регистратор»; для регистров сведений, неподчиненных регистратору — поля, соответствующие изменениям, входящим в основной отбор регистра; для констант — идентификатор объекта метаданных Константы.
- индексируются данные в «соответствии»
Явным способом включением свойства «Индексировать» реквизитов и измерений с значение «Индексировать» и «Индексировать с доп. Упорядочиванием». Вариант ««Индексировать с доп. Упорядочиванием»» включает обычно колонку «код» или «наименование» в индекс.
Еще одним явным способом можно считать добавление объекта метаданных в объект метаданных «критерий отбора».
Можно указать индекс для таблицы значений и в запросах для временных таблиц.
ВЫБРАТЬ
Код,
Наименование
ПОМЕСТИТЬ ВременнаяТаблица
ИЗ Справочник.Номенклатура
ИНДЕКСИРОВАТЬ ПО Код
В любом случае, надо понимать, что говоря об индексах, мы фактически подразумеваем индексы СУБД, которая используется для 1С:Предприятие. Исключению составляют объекты типа Таблица значений, когда индексы находятся в RAM (оперативной памяти).
Физическая сущность индексов в MS SQL Server.
Физически данные хранятся на 8Кб страницах. Сразу после создания, пока таблица не имеет индексов, таблица выглядит как куча (heap) данных. Записи не имеют определенного порядка хранения.
Когда вы хотите получить доступ к данным, SQL Server будет производить сканирование таблицы (table scan). SQL Server сканирует всю таблицу, что бы найти искомые записи.
Отсюда становятся понятными базовые функции индексов:
— увеличение скорости доступа к данным,
— поддержка уникальности данных.
Несмотря на достоинства, индексы так же имеют и ряд недостатков. Первый из них – индексы занимают дополнительное место на диске и в оперативной памяти. Каждый раз когда вы создаете индекс, вы сохраняете ключи в порядке убывания или возрастания, которые могут иметь многоуровневую структуру. И чем больше/длиннее ключ, тем больше размер индекса. Второй недостаток – замедляются операции вставки, обновления и удаления записей.
В среде MS SQL Server реализовано несколько типов индексов:
- некластерные индексы;
- кластерные (или кластеризованные) индексы;
- уникальные индексы;
- индексы с включенными столбцами
- индексированные представления
- полнотекстовый
- XML
Некластерный индекс
Некластерные индексы – не перестраивают физическую структуру таблицы, а лишь организуют ссылки на соответствующие строки.
Для идентификации нужной строки в таблице некластерный индекс организует специальные указатели, включающие в себя:
- информацию об идентификационном номере файла, в котором хранится строка;
- идентификационный номер страницы соответствующих данных;
- номер искомой строки на соответствующей странице;
- содержимое столбца.
Некластерных индексов может быть несколько для одной таблицы.
Некластеризованный индекс по таблице, не имеющей кластеризованного индекса
Некластеризованный индекс по таблице, имеющей кластеризованный индекс
Кластерный (кластеризованный) индекс
Принципиальным отличием кластерного индекса от индексов других типов является то, что при его определении в таблице физическое расположение данных перестраивается в соответствии со структурой индекса. Логическая структура таблицы в этом случае представляет собой скорее словарь, чем индекс. Данные в словаре физически упорядочены, например по алфавиту.
Кластерные индексы могут дать существенное увеличение производительности поиска данных даже по сравнению с обычными индексами. Увеличение производительности особенно заметно при работе с последовательными данными. Если в таблице определен некластерный индекс, то сервер должен сначала обратиться к индексу, а затем найти нужную строку в таблице. При использовании кластерных индексов следующая порция данных располагается сразу после найденных ранее данных. Благодаря этому отпадают лишние операции, связанные с обращением к индексу и новым поиском нужной строки в таблице.
Естественно, в таблице может быть определен только один кластерный индекс. Кластерный индекс может включать несколько столбцов.
Необходимо избегать создания кластерного индекса для часто изменяемых столбцов, поскольку сервер должен будет выполнять физическое перемещение всех данных в таблице, чтобы они находились в упорядоченном состоянии, как того требует кластерный индекс. Для интенсивно изменяемых столбцов лучше подходит некластерный индекс.
При создании в таблице первичного ключа (PRIMARY KEY) сервер автоматически создает для него кластерный индекс, если его не существовало ранее или если при определении ключа не был явно указан другой тип индекса.
Когда же в таблице определен еще и некластерный индекс, то его указатель ссылается не на физическое положение строки в базе данных, а на соответствующий элемент кластерного индекса, описывающего эту строку, что позволяет не перестраивать структуру некластерных индексов всякий раз, когда кластерный индекс меняет физический порядок строк в таблице.
Уникальный индекс
Уникальность значений в индексируемом столбце гарантируют уникальные индексы. При их наличии сервер не разрешит вставить новое или изменить существующее значение таким образом, чтобы в результате этой операции в столбце появились два одинаковых значения.
Уникальный индекс является своеобразной надстройкой и может быть реализован как для кластерного, так и для некластерного индекса. В одной таблице может существовать один уникальный кластерный и множество уникальных некластерных индексов.
Уникальные индексы следует определять только тогда, когда это действительно необходимо. Для обеспечения целостности данных в столбце можно определить ограничение целостности UNIQUE или PRIMARY KEY, а не прибегать к уникальным индексам. Их использование только для обеспечения целостности данных является неоправданной тратой пространства в базе данных. Кроме того, на их поддержание тратится и процессорное время.
1С:Предприятие 8 активно использует кластерные уникальные индексы. Это означает, что можно получить ошибку не уникального индекса.
Если не уникальность заключается в датах с нулевыми значениями, то проблема решается созданием базы с параметром смещения равным 2000.
«Рыба» скрипта для определения не уникальных записей:
SELECT COUNT(*) Counter, <перечисление всех полей соответствующего индекса> from <имя таблицы>
GROUP BY <перечисление всех полей соответствующего индекса>
HAVING Counter > 1
Понятие первичного и внешнего ключа
Первичный ключ (primary key) – это набор столбцов таблицы, значения которых уникально определяют строку.
Внешний ключ (foreign key) . Внешним ключом называется поле таблицы, предназначенное для хранения значения первичного ключа другой таблицы с целью организации связи между этими таблицами. Внешний ключ в таблице может ссылаться и на саму эту таблицу. Такие внешние ключи, в основном, используются для хранения древовидной структуры данных в реляционной таблице. СУБД поддерживают автоматический контроль ссылочной целостности на внешних ключах.
1С не использует внешние ключи. Ссылочная целостность обеспечивается логикой приложения.
Ограничения индексов
Индекс может быть создан на основании нескольких полей. В этом случае существует ограничение – длина ключа индекса не должна превышать 900 байтов и не более 16 ключевых столбцов. На практике это означает что при создании индекса, включающего более 16 полей, индекс усекается. Это может оказать влияние на производительность при количестве субконто составного типа более 4х.
В актуальных релизах платформы выполнена оптимизация данного случая и используется хэш по ключу полей, но это медленней «полноценных» индексов.
Статистика индексов
Microsoft SQL Server собирает статистику по индексам и полям данных, хранимых в базе. Эта статистика используется оптимизатором запроса SQL Server при выборе оптимального плана исполнения запросов на выборку или обновление данных.
При создании индекса оптимизатор запросов автоматически сохраняет данные статистики о проиндексированых столбцах.
Просмотр статистики — sp_helpstats.
Фрагментация индексов
Чрезмерная фрагментация создает проблемы для больших операций ввода-вывода. Фрагментация не должна превышать 25%. От снижения фрагментации индексов могут выиграть операции сканирования больших диапазонов данных. Для этого рекомендуется выполнять периодическую дефрагментацию индексов. Обратите внимание, что при дефрагментации индексов (по умолчанию) автоматически обновляется статистика.
Смотреть степень фрагментированности индексов можно штатными средствами СУБД или в разрезе объектов метаданных можно например с помощью бесплатного онлайн-сервиса http://www.gilev.ru/sqlsize/
Оптимизация размещения индексов
При объеме таблиц не позволяющем им «разместиться» в оперативной памяти сервера, на первое место выходит скорость дисковой подсистемы (I/O). И здесь можно обратить внимание возможность размещать индексы в отдельных файлах расположенных на разных жестких дисках.
Подробное описание действий http://technet.microsoft.com/ru-ru/library/ms175905.aspx
Использование индекса из другой файловой группы повышает производительность некластерных индексов в связи с параллельностью выполнения процессов ввода/вывода и работы с самим индексом.
Для определения размеров можно использовать выше упомянутую обработку.
Влияние индексов на блокировки
Отсутствие необходимого индекса для запроса означает перебор всех записей таблицы, что в свою очередь приводит к избыточным блокировкам, т.е. блокируются лишние записи. Кроме того, чем дольше выполняется запрос из-за отсутствующих индексов, тем больше время удержания блокировок.
Другая причина блокировок — малое количество записей в таблицах. В связи с этим SQL Server, при выборе плана выполнения запроса, не использует индексы, а обходит всю таблицу(Table Scan), блокируя целиком. Для того, чтобы избежать подобных блокировок, необходимо увеличить количество записей в таблицах до 1500-2000. В этом случае сканирование таблицы становится долее дорогостоящей операцией и SQL Server начинает использовать индексы. Конечно это можно сделать не всегда, ряд справочников как «Организации», «Склады», «Подразделения» и т.п. обычно имеют мало записей. В этих случаях индексирование не будет улучшать работу.
Эффективность индексов
Мы уже отметили в заголовке статьи, что нас интересуют влияние индексов на быстродействие запросов. Итак, индексы наиболее подходят для задач следующего типа:
- Запросы, которые указывают «узкие» критерии поиска. Такие запросы должны считывать лишь небольшое число строк, отвечающих определенным критериям.
- Запросы, которые указывают диапазон значений. Эти запросы также должны считывать небольшое количество строк.
- Поиск, который используется в операциях связывания. Колонки, которые часто используются как ключи связывания, прекрасно подходят для индексов.
- Поиск, при котором данные считываются в определенном порядке. Если результирующий набор данных должен быть отсортирован в порядке кластеризованного индекса, то сортировка не нужна, поскольку результирующий набор данных уже заранее отсортирован. Например, если кластеризованный индекс создан по колонкам lastname (фамилия), firstname (имя), а для приложения требуется сортировка по фамилии и затем по имени, то здесь нет необходимости добавлять инструкцию ORDER BY.
Правда при всей полезности индексов, есть одно очень важное НО – индекс должен быть «эффективно используемым» и должен позволять находить данные с использованием меньшего числа операций ввода-вывода и объема системных ресурсов. И наоборот, неиспользуемые (редко используемые) индексы скорее ухудшают скорость записи данных (поскольку каждая операция, изменяющая данные, должна также обновлять страницы индексов) и создают избыточный объем базы.
Покрывающим (для данного запроса), называется индекс в котором есть все необходимые поля для этого запроса. Например, если индекс создан по колонкам a, b и c, а оператор SELECT запрашивает данные только из этих колонок, то требуется доступ только к индексу.
Что такое кластерный индекс 1с
1C:Enterprise | .NET Core
Индексы
Хочу рассмотреть вопросы и подготовку к сертификации. Учить правильные ответы плохой путь, а вот понимать ответы и применять их, вы тем самым становитесь на путь к уровню «1С:Эксперт». По сути это цикл записей с расширенными ответами на несколько вопросов из тестов.
Структуру индексов регистра сведений можно определить в режиме 1С:Предприятия, использовав функцию глобального контекста «ПолучитьСтруктуруХраненияБазыДанных».
Источники:
• ИТС: Индексирование таблиц
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 84.
Для периодического регистра сведений , в том числе подчиненного регистратору , кластерным индексом является (кроме регистров с периодичностью «по позиции регистратора»):
[ОРРХ | ОРНР1 + …] + Период + [Измерение 1 + …] — для 8.2;
[ОРРХ | ОРНР1 + …] [Измерение 1 + …] + Период — для 8.3, где
ОРРХ — если в конфигурации определены разделители, то в индексы может входит поле, которое содержит значение хэш-функции набора значений разделителей;
ОРНР — общие реквизиты, являющихся разделителями в режиме «независимо».
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 89.
• ИТС: Индексы таблиц базы данных
Для периодического регистра сведений , подчиненного регистратору , с периодичностью « по позиции регистратора » кластерным индексом является:
[ОРРХ | ОРНР1 + …] [Измерение 1 + …] + Период + Регистратор + НомерСтроки, где
ОРРХ — если в конфигурации определены разделители, то в индексы может входит поле, которое содержит значение хэш-функции набора значений разделителей;
ОРНР — общие реквизиты, являющихся разделителями в режиме «независимо».
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 90, с. 92 (описаны отличия для «1С:Предприятие 8.3»).
• ИТС: Индексы таблиц базы данных
Кластерный индекс для баз на платформе «1С:Предприятие 8.3» и СУБД MS SQL Server периодического регистра сведений , подчиненного регистратору , имеет вид (если нет общих реквизитов, являющихся разделителями):
[Измерение 1 + …] + Период
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 92.
Для ведущего измерения регистра сведений «1С:Предприятие» возможность включения индексирования в конфигураторе отключена. Для измерений свойство доступно для редактирования, если измерение не является ведущим. Для ведущих измерений индекс создается всегда.
Для периодического регистра сведений всегда будет создаваться индекс:
[ОРРХ | ОРНР1 + …] + Период + [Измерение 1 + …] — для 8.2;
[ОРРХ | ОРНР1 + …] [Измерение 1 + …] + Период — для 8.3.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 89.
• ИТС: Индексы таблиц базы данных
Индексы повышают производительность поиска, сортировки данных и производительность СУБД.
Источники:
• wiki: Индекс (базы данных)
Платформа «1С:Предприятие» создает два типа индексов — кластерные и некластерные индексы. Индексы создаются неявным образом при создании объектов конфигурации, а так при использовании различных настроек объектов. Так же индекс можно создать явным образом, например, включением свойства «Индексировать».
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 87.
• ИТС: Индексы таблиц базы данных
Кластеризованный индекс — индекс, по значению которого отсортированы и хранятся строки данных в таблицах БД. Таблица может иметь только один кластерный индекс или иметь ни одного. Может быть уникальным, так и не уникальным (обеспечивается СУБД путем добавления служебных данных).
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 86.
• MSDN: Индексы
Кластеризованный индекс это тоже самое что и кластерный индекс.
Уникальный индекс — реализует ограничение целостности на таблице, исключая возможность вставки повторяющихся значений. Может быть либо кластерным, либо не кластерным.
Источники:
• MSDN: Индексы
• wiki: Индекс (базы данных)
Кластерный индекс — индекс, по значению которого отсортированы и хранятся строки данных в таблицах БД. Таблица может иметь только один кластерный индекс или иметь ни одного. Может быть уникальным, так и не уникальным (обеспечивается СУБД путем добавления служебных данных).
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 86.
• MSDN: Индексы
Уникальный индекс — реализует ограничение целостности на таблице, исключая возможность вставки повторяющихся значений. Может быть либо кластерным, либо не кластерным.
Источники:
• MSDN: Индексы
• wiki: Индекс (базы данных)
Кластерный индекс — индекс, по значению которого отсортированы и хранятся строки данных в таблицах БД. Таблица может иметь только один кластерный индекс или иметь ни одного. Может быть уникальным, так и не уникальным (обеспечивается СУБД путем добавления служебных данных).
Некластерный индекс — индекс, который содержит только указатели на записи таблицы. Таблица может иметь несколько различных некластерных индексов, каждый из которых определяет свой собственный порядок следования записей. Может быть уникальным, так и не уникальным.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 86.
• MSDN: Индексы
• wiki: Индекс (базы данных)
Наличие индексов не есть обязательным.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 86.
• MSDN: Индексы
• wiki: Индекс (базы данных)
Индексирование первого измерения регистра, если это измерение имеет составной тип может улучшить план запроса. В некоторых случаях этот индекс может работать намного эффективнее — чем использование конструкции ВЫРАЗИТЬ(. КАК . ) .
B-tree — структура данных, дерево поиска . С точки зрения внешнего логического представления, сбалансированное (длина любых двух путей от корня до листьев различается не более, чем на единицу), сильно ветвистое дерево (свойство каждого узла дерева ссылаться на большое число узлов-потомков). Часто используется для хранения данных во внешней памяти.
Binary tree — иерархическая структура данных, в которой каждый узел имеет не более двух потомков (детей). Как правило, первый называется родительским узлом, а дети называются левым и правым наследниками.
Источники:
• wiki: B-дерево
• wiki: Двоичное дерево
Binary search tree — это двоичное дерево, для которого выполняются следующие дополнительные условия:
• оба поддерева — левое и правое — являются двоичными деревьями поиска;
• у всех узлов левого поддерева произвольного узла X значения ключей данных меньше, нежели значение ключа данных самого узла X;
• у всех узлов правого поддерева произвольного узла X значения ключей данных больше либо равно, нежели значение ключа данных самого узла X.
Источники:
• wiki: Двоичное дерево поиска
Binary heap — такое двоичное дерево, для которого выполнены три условия:
• значение в любой вершине не меньше, чем значения её потомков;
• глубина всех листьев (расстояние до корня) отличается не более чем на 1 слой;
• последний слой заполняется слева направо без «дырок».
Источники:
• wiki: Двоичная куча
Структура внутренних и листовых страниц B-tree обычно отличается.
Источники:
• wiki: B-дерево
Ссылочная целостность (Referential integrity) — необходимое качество реляционной базы данных, заключающееся в отсутствии в любом её отношении внешних ключей, ссылающихся на несуществующие кортежи.
Источники:
• wiki: Ссылочная целостность
Кортеж — упорядоченный набор фиксированной длины.
Источники:
• wiki: Кортеж (информатика)
Внешний ключ (Foreign key) — это столбец или сочетание столбцов, которое применяется для принудительного установления связи между данными в двух таблицах.
Внешний ключ можно создать, определив ограничение FOREIGN KEY при создании или изменении таблицы.Если один или несколько столбцов, в которых находится первичный ключ для одной таблицы, упоминается в одном или нескольких столбцах другой таблицы, то в ссылке внешнего ключа создается связь между двумя таблицами. Этот столбец становится внешним ключом во второй таблице.
Источники:
• wiki: Внешний ключ
• MSDN: Ограничения FOREIGN KEY
Потенциальный ключ (Candidate key) — в реляционной модели данных — подмножество атрибутов отношения, удовлетворяющее требованиям уникальности и минимальности ( несократимости ).
Источники:
• wiki: Потенциальный ключ
• MSDN: Альтернативный ключ
Целостность базы данных (Database integrity) — соответствие имеющейся в базе данных информации её внутренней логике, структуре и всем явно заданным правилам. Каждое правило, налагающее некоторое ограничение на возможное состояние базы данных, называется ограничением целостности (integrity constraint).
Источники:
• wiki: Целостность базы данных
Первичный ключ (Primary key) — в реляционной модели данных один из потенциальных ключей отношения, выбранный в качестве основного ключа (или ключа по умолчанию). С точки зрения теории все потенциальные ключи отношения эквивалентны, то есть обладают одинаковыми свойствами уникальности и минимальности . Однако в качестве первичного обычно выбирается тот из потенциальных ключей, который наиболее удобен для тех или иных практических целей.
Источники:
• wiki: Первичный ключ
Часть индексов могут содержать значения хэш-функции значений разделителей — если тип разделителя — «Строка», или разделитель независимый и совместный, или разделителей больше одного.
Источники:
• ИТС: Индексы таблиц базы данных
С помощью MS SQL Management Studio можно увидеть, включен ли общий реквизит в состав индекса.
Способ получения существующих индексов с помощью конфигуратора, процедуры ПолучитьСтруктуруХраненияБазыДанных() имеет несколько ограничений:
- Для всех объектных типов данных (справочники, документы и т. д.) в базе автоматически определяется кластерный индекс по полю Ссылка. И вот его в этой таблице вы не увидите, но про него надо просто помнить;
- При использовании общего реквизита к большинству индексов первым столбцом ключа индекса добавляется DataSeparationHash. Этого факта, к сожалению, через ПолучитьСтруктуруХраненияБазыДанных() вы тоже не увидите, и про него тоже надо помнить;
- Также в этой таблице вы не увидите индекса simplekey для регистра сведений.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 86.
Общий реквизит, последовательность общих реквизитов, хэш-функция общих реквизитов включаются в состав индекса первым полем.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 87-92.
• ИТС: Индексы таблиц базы данных
Если в конфигурации используется более одного общего реквизита, являющихся независимыми разделителями (ОРНР), в часть индексов включается их последовательность (ОРНР1 + ОРНР2 + … +) и в часть индексов включается их хэш-функция.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 87-92.
• ИТС: Индексы таблиц базы данных
Для непериодического регистра сведений , подчиненного регистратору , кластерным индексом является:
[ОРНР1 + … +] Регистратор + НомерСтроки, где
ОРНР — общие реквизиты, являющихся разделителями в режиме «независимо».
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 90.
• ИТС: Индексы таблиц базы данных
Для справочников , документов , планов видов характеристик , планов обменов , планов счетов , планов видов расчета , бизнес-процессов , точек маршрута бизнес-процессов , задач , кластерным индексом является:
[ОРНР1 + … +] Ссылка, где
ОРНР — общие реквизиты, являющихся разделителями в режиме «независимо».
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 88.
• ИТС: Индексы таблиц базы данных
Если в конфигурации определен единственный независимый разделитель, тип которого не «Строка», тогда в соответствующих индексах используется значение разделителя.
Источники:
• ИТС: Индексы таблиц базы данных
Разделитель итогов (Splitter) включается в состав индекса последним.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 91.
• ИТС: Индексы таблиц базы данных
Разделитель итогов (Splitter) включается в состав индекса если для регистра разрешено разделение итогов.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 91.
• ИТС: Индексы таблиц базы данных
Включен ли разделитель итогов (Splitter) в состав индекса можно увидеть с помощью MS SQL Management Studio и с помощью процедуры ПолучитьСтруктуруХраненияБазыДанных().
Разделитель итогов (Splitter) в индексе таблицы оборотов регистра накопления применяется для типа регистра накопления « Обороты ».
Источники:
• ИТС: Индексы таблиц базы данных
Разделитель итогов (Splitter) в индексе таблицы остатков регистра накопления применяется для типа регистра накопления « Остатки ».
Источники:
• ИТС: Индексы таблиц базы данных
При индексировании измерения ИзмерениеN регистра накопления , регистра бухгалтерии , регистра сведений с периодичностью « по позиции регистратора » будет создан индекс по таблице движений:
[ОРРХ | ОРНР1 + …] ИзмерениеN + Период + Регистратор + НомерСтроки, где
ОРРХ — если в конфигурации определены разделители, то в индексы может входит поле, которое содержит значение хэш-функции набора значений разделителей;
ОРНР — общие реквизиты, являющихся разделителями в режиме «независимо».
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 90.
• ИТС: Индексы таблиц базы данных
SimpleKey — короткий ключ записи регистра. Поле присутствует у непериодических регистров сведений , имеющих хотя бы одно измерение.
Источники:
• книга «Настольная книга 1С:Эксперта по технологическим вопросам», издание 2, с. 89.
• ИТС: Индексы таблиц базы данных
Splitter — разделитель итогов. (См. пункт 36, 37, 38)
Некоторые индексы первым полем содержат поле с именем, не относящимся к колонкам таблиц регистра, и не являющимся ни периодом, ни регистратором. В составе имени поля есть «Field» — это ОРНР, общий реквизит, являющийся разделителям в режиме «независимо».
Источники:
• ИТС: Индексы таблиц базы данных
Индексировать ресурсы можно только у регистра сведений .
Индексировать реквизиты можно у регистров всех типов .
Если для реквизита «Реквизит» свойство «Индексировать» установлено в значение «Индексировать с доп. упорядочиванием» и при этом свойство «Длина кода» не равно 0, а свойство «Основное представление» равно «В виде кода», индекс будет вида:
[ОРРХ | ОРНР1 + …] Реквизит + Код + Ссылка
Если для реквизита «Реквизит» свойство «Индексировать» установлено в значение «Индексировать с доп. упорядочиванием» и при этом свойство «Длина наименования» не равно 0, а свойство «основное представление» равно «В виде наименования», индекс будет вида:
[ОРРХ | ОРНР1 +…] Реквизит + Наименование + Ссылка
Источники:
• ИТС: Индексы таблиц базы данных
Индексировать измерение регистра можно если оно не ведущее. (См. пункт 5)
Если измерению «ИзмерениеN» периодического регистра сведений (НЕ с периодичностью «по позиции регистратора») задано свойство «Индексировать» или свойство «Ведущее» и при этом это не единственное измерение, индекс будет вида:
[ОРРХ | ОРНР1 +…] ИзмерениеN + Период + Измерение1 + [Измерение2 +…], где
ОРРХ — если в конфигурации определены разделители, то в индексы может входит поле, которое содержит значение хэш-функции набора значений разделителей;
ОРНР — общие реквизиты, являющихся разделителями в режиме «независимо».
Источники:
• ИТС: Индексы таблиц базы данных
Если измерению «ИзмерениеN» непериодического регистра сведений задано свойство «Индексировать» или свойство «Ведущее» и при этом это не первое и не единственное измерение, индекс будет вида:
[ОРРХ | ОРНР1 +…] ИзмерениеN + Измерение1 + [Измерение2 +…], где
ОРРХ — если в конфигурации определены разделители, то в индексы может входит поле, которое содержит значение хэш-функции набора значений разделителей;
ОРНР — общие реквизиты, являющихся разделителями в режиме «независимо».
Источники:
• ИТС: Индексы таблиц базы данных
Поле с именем DataSeparationHash — хэш-функция общих реквизитов, являющихся разделителями.
Индексы в 1С 8.3 и 8.2
ПАО «НИКО-БАНК» выражает свою благодарность за оперативную и грамотную работу.
В условиях постоянно меняющегося законодательства Банк заинтересован иметь полную и актуальную номативную базу. Это обеспечивается использованием Банком справочно-нормативной системы «Гарант».
Безусловным плюсом в работе компании «МастерСофт» является быстрое реагирование сотрудников при предоставлении документов по запросу Банка, принятых до обновления справочно-правовой системы.
Коллектив компании «АЭРОПОРТ ОРЕНБУРГ» выражает благодарность за взаимовыгодное сотрудничество с МастерСофт-ИТ. Оперативная поставка антивирусных программ Dr. Web обеспечила надежную защиту нашей компьтерной сети.
Особая благодарность сотрудникам Департамента продаж СЦ ИТ за профессиональный подход в решении всех возникающих задач.
ООО «Орский Вагонный Завод» выражает искреннюю благодраность за качество обслуживания вашими специалистами. Консультации и поставка антивирусов всегда проходят оперативно и на высоком профессиональном уровне.
Уверены, что и в дальнейшем наше сотрудничество на взаимовыгодных условиях продолжится.
Главный бухгалтер муниципального бюджетного учреждения дополнительного образования «Дворец творчества детей и молодёжи» Кетерер Татьяна Михайловна выражает благодарность специалистам МастерСофт:
«Я хотела бы объявить благодарность вашим сотрудникам. Работает с нами по программе «1С: Бухгалтерия бюджетного учреждения 8» непосредственно Шевлягина Юлия.
Так же огромная благодарность за отзывчивость, терпение и квалифицированную, своевременную помощь Набокиной Олесе и Ерёменко Татьяне (они нас сопровождают по программе «Зарплата и Кадры»).
Им очень с нами тяжело, но они терпеливо продолжают сотрудничать. С вами очень надёжно. Конечно же наши ошибки есть и без вас мы бы вообще о них не знали и в суде, наверное, судились бы. А сейчас мы решаем вопросы. «.