Очень часто, в базах 1С организуют хранилище файлов, что удобно. Например, удобно хранить переписку по электронной почте. И вот, представьте, вы берете к-нить картинку и отправляете 8 своим коллегам, с точки зрения обычного хранения данных, в вашей БД, сохранится 9 копий данной картинки, а если ваши коллеги начнут отвечать на письмо, то и больше.
Вот так это выглядит;одинаковые файлы, записываются несколько раз:
Хранение файлов в базе 1С можно оптимизировать для уменьшения размера хранимых данных. В этом нам поможет теория, что ХЕШ-функции способны определить изменения даже одного бита в файле.
1С поддерживает следующие алгоритмы, но можно использовать и внешние вызовы, для получения ХЕШа:
- CRC32 (CRC32)
- MD5 (MD5)
- SHA1 (SHA1)
- SHA256 (SHA256)
Основная идея в том, что прежде чем записать в БД файл, проверить - существует ли точно такой же файл или нет? Если существует, то записывать его нет необходимости, и можно сохранить только необходимые реквизиты - имя файла, ссылка на объект и т.д.
Для ускорения перебора файлов, мы сначала ищем их по размеру (число) потом уже по ХЕШу. Для чего у нас есть регистр:
Если файл не найден, мы его записываем в хранилище значений, если найден, то записываем ссылку на него, уже в другом регистре:
Я провел эксперимент, взял БД с обычным хранением файлов и БД с переделанным под описанный выше алгоритм хранением. После чего, в 2 БД были загружены письма за 1 год. Результат - база данных стала более чем в 3 раза меньше:
Для "усиления" надежности, можно использовать несколько ХЕШ-функций. Падения быстродействия при загрузке почты, я не заметил.
Немного кода:
&НаСервере
Функция ПолучитьРазмерФайлаИХешМД5(_ХранилищеЗначения) Экспорт
Структура = новый Структура;
ДД = _ХранилищеЗначения.Получить();
РазмерФайла = ДД.Размер();
Хэш = Новый ХешированиеДанных(ХешФункция.MD5);
Хэш.Добавить(ДД);
МД5Двоичный = Хэш.ХешСумма;
Результат = ПолучитьHexСтрокуИзДвоичныхДанных(МД5Двоичный);
ХешМД5 = Результат;
Структура.Вставить("РазмерФайла", РазмерФайла);
Структура.Вставить("ХешМД5", ХешМД5);
Возврат Структура;
КонецФункции
&НаСервере
Процедура ЗаполнитьВложения(_Файлы, ИмяВложения, ИнтернетПочтовоеСообщение, _Владелец) Экспорт
Для Каждого ИнтернетПочтовоеВложение Из ИнтернетПочтовоеСообщение.Вложения Цикл
ИмяВложения = ИнтернетПочтовоеВложение.Имя;
Если ТипЗнч(ИнтернетПочтовоеВложение.Данные) = Тип("ДвоичныеДанные") Тогда
СтруктураФайла = Новый Структура;
СтруктураФайла.Вставить("ИмяФайла", ИмяВложения);
СтруктураФайла.Вставить("Идентификатор", ИнтернетПочтовоеВложение.Идентификатор);
СтруктураФайла.Вставить("ПутьКФайлу", "");
СтруктураФайла.Вставить("ХранилищеЗначения" , Новый ХранилищеЗначения(ИнтернетПочтовоеВложение.Данные, Новый СжатиеДанных()));
СтруктураФайла.Вставить("СпособКодирования", Строка(ИнтернетПочтовоеВложение.СпособКодирования));
СтруктураФайла.Вставить("ТипСодержимого", ИнтернетПочтовоеВложение.ТипСодержимого);
Файл = СоздатьФайл(_Владелец, СтруктураФайла);
НовоеФайлы = _Файлы.Добавить();
НовоеФайлы.Файл = Файл.Ссылка;
Иначе
ЗаполнитьВложения(_Файлы, ИмяВложения, ИнтернетПочтовоеВложение.Данные, _Владелец);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаСервере
Функция СоздатьФайл(_Владелец, _СтруктураФайла) Экспорт
ИмяФайла = _СтруктураФайла.ИмяФайла;
Идентификатор = _СтруктураФайла.Идентификатор;
ПутьКФайлу = _СтруктураФайла.ПутьКФайлу;
ХранилищеЗначения = _СтруктураФайла.ХранилищеЗначения;
СпособКодирования = _СтруктураФайла.СпособКодирования;
ТипСодержимого = _СтруктураФайла.ТипСодержимого;
СтруктураФайла = ПолучитьРазмерФайлаИХешМД5(ХранилищеЗначения);
ФайлСсылка = НайтиФайлПоРазмеруФайлаИХешуМД5(СтруктураФайла.РазмерФайла, СтруктураФайла.ХешМД5);
Если ФайлСсылка = неопределено Тогда
Файл = Справочники.Файлы.СоздатьЭлемент();
Файл.Наименование = ИмяФайла;
Файл.ДанныеФайла = ХранилищеЗначения;
Файл.Записать();
ФайлСсылка = Файл.Ссылка;
КонецЕсли;
Регистр = РегистрыСведений.Регистр_ВладельцыФайлов.СоздатьНаборЗаписей();
Регистр.Отбор.Источник.Установить(ФайлСсылка);
Регистр.Отбор.ВладелецФайла.Установить(_Владелец);
Регистр.Прочитать();
Если Регистр.Количество() = 0 Тогда
ЗаписьРегистра = Регистр.Добавить();
ЗаписьРегистра.Источник = ФайлСсылка;
ЗаписьРегистра.ВладелецФайла = _Владелец;
ИначеЕсли Регистр.Количество() > 1 Тогда
ВызватьИсключение "В регистре Регистр_ВладельцыФайлов для (" + Строка(ФайлСсылка) + ") найдено несколько одинаковых значений. Сообщите разработчику. Сделайте скриншот вводимых данных.";
Иначе
ЗаписьРегистра = Регистр[0];
КонецЕсли;
ЗаписьРегистра.ИмяФайла = ИмяФайла;
ЗаписьРегистра.Идентификатор = Идентификатор;
ЗаписьРегистра.ПутьКФайлу = ПутьКФайлу;
ЗаписьРегистра.СпособКодирования = СпособКодирования;
ЗаписьРегистра.ТипСодержимого = ТипСодержимого;
Регистр.Записать();
Возврат ФайлСсылка;
КонецФункции