gifts2017

Быстрое получение уникального числового значения без блокировок

Опубликовал Сергей Зверев (serferian) в раздел Программирование - Универсальные функции

Столкнулся с проблемой блокировок/тормозов при назначении уникального Штрихкода.
Работало через поиск Макс. значения в Регистре сведений и записи туда нового значения.
В принципе данный функционал можно использовать для создания уникальныхзначений.
Только для Клиент-серверного режима работы. Только для SQL Server, но думаю что похожий функционал можно сделать и на Oracle/Postgre

Решение созрело такое:
1. Создается табличка прямо в БД 1С вида:
(для MS SQL Server)
CREATE TABLE [dbo].[zbarcodes](
    [ID] [bigint] IDENTITY([НАЧАЛЬНОЕЗНАЧЕНИЕСЧЕТЧИКА],1) NOT NULL,
 CONSTRAINT [PK_zbarcodes] PRIMARY KEY CLUSTERED
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

[НАЧАЛЬНОЕЗНАЧЕНИЕСЧЕТЧИКА]  - заменить на число, например 20000000000 - первый шк в системе (или другое число - последнее в Вашей учетной системе, если хочется продолжить текущую нумерацию)

2. Создаем Процедурку в общем модуле с выполнением на сервере (для УФ)

//Процедура получения уникального штрихкода (у нас используется Code 128 - без контрольных символов)
//Параметры:
//Организация - можно сделать таблицы на каждую организацию - и получать по организации имя таблицы в БД из ЗначенияСвойствОбъектов
//СтруктураПараметров - параметры подключения к БД SQL через ADO

Функция ПолучитьНовыйШтрихкод(Организация, СтруктураПараметров) Экспорт
   
   Соединение= Новый COMObject("ADODB.Connection");
    Соединение.ConnectionString = "Driver={SQL Server};Server=" + СокрЛП(СтруктураПараметров.Сервер)
                                                        + ";UID=" + СокрЛП(СтруктураПараметров.Логин)
                                                        + ";pwd=" + СокрЛП(СтруктураПараметров.Пароль)
                                                        + ";Database=" + СокрЛП(СтруктураПараметров.БД);
    Соединение.ConnectionTimeOut = 40;
    Соединение.CommandTimeout = 0;
    Соединение.CursorLocation = 3;
    Попытка
        Соединение.Open();
    Исключение
        #Если Клиент Тогда
        Сообщить("Возникла ошибка подключения к базе");
        #КонецЕсли
        Соединение="";
        Возврат Неопределено;
    КонецПопытки;

        ТекстЗапроса="INSERT into zbarcodes DEFAULT VALUES";
        Соединение.Execute(ТекстЗапроса);   
       
        ТекстЗапроса="SELECT @@IDENTITY";
        Выборка=Соединение.Execute(ТекстЗапроса);       
       
        НовыйШтрихкод=Выборка.Fields(0).Value;
       
        Выборка.Close(); 
        Выборка =""
        Соединение = "";

    Возврат  НовыйШтрихкод;
   
КонецФункции

В итоге все забыли что такое блокировки при присвоении новых штрихкодов, и обеспечена уникальность штрихкода, + скорость получения значения!

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Юрий Гончарук (yukon) 13.08.14 12:17
у нас используется Code 128

Так можно вообще не заморачиваться с базой данных и т.п.
Функция ПолучитьНовыйШтрихкод()
    Возврат СтрЗаменить(ВРег(Новый УникальныйИдентификатор()),"-", "");
КонецФункции
...Показать Скрыть
2. Сергей Зверев (serferian) 13.08.14 12:18
ага, только ручками очень тяжко потом это всё набирать в случае отказа сканера ШК
3. pvase (pvase) 13.08.14 12:28
(2) serferian,
Если интересует без тормозов, то лучше создать массив уникальных значений а потом лишь их использовать. Не думаю что регистр сведений для этого лучшая реализация. Ведь требуется всего 3 поля, 1 - ID, 2 - значение, 3 - уже используется. Если вам подойдет на таблице SQL, то можете создать такую таблицу, заполнить ее значениями от 1 до 1000 000 000 и пользоваться ими.
4. Константин Юрин (kostyaomsk) 13.08.14 12:56
Или если уж очень нужна сквозная нумерация по группам то нужно переработать механизм записи штрих-кода: после успешной записи новой записи в регистр Штрихкоды для очередной номенклатуры записывать последнее значение в константу. При следующей генерации смотреть значение константы, увеличивать соответствующий разряд на единицу и писать в регистр.
А вот если нумерация сквозная, но сложная по каким-то группам (и прочим условиям), то нужно все хранить в ХранилищеЗначения и делать доступ наподобие стандартной функции чтения данных констант и учетной политики регистров как в стандартных конфигурациях:
Функция ПолучитьЗначениеПеременной(ИмяПараметра, Кэш = Неопределено, КэшИзменен = Ложь) Экспорт
	
	Если Кэш = Неопределено Тогда
		// Кэш не был проинициализирован
		Кэш = Новый Структура;
	Иначе
		// Ищем значение в структуре
		НайденноеЗначение = Неопределено;
		Если Кэш.Свойство(ИмяПараметра, НайденноеЗначение) Тогда
			Возврат НайденноеЗначение;
		КонецЕсли;
	КонецЕсли;
//Дальше куча условий по  ....
// В конце если ничего не нашли, то читаем из константы с переданным именем и записываем ее в нашу переменную "КЭШ".
// Следующий доступ к нужному параметру в рамках сеанса будет уже из ОЗУ до момента изменения в базе. Тогда уже будет автоматическое перезаполнение переменной в "КЭШ"

Иначе
		НайденноеЗначение = Константы[ВРег(ИмяПараметра)].Получить();
		
		//СтрокаИсключения = "Невозможно обработать параметр " + """" + ИмяПараметра + """" + " для получения значения";
		//ВызватьИсключение СтрокаИсключения;
	КонецЕсли;
	
	Кэш.Вставить(ИмяПараметра, НайденноеЗначение);
	КэшИзменен = Истина;
	
	Возврат НайденноеЗначение;
	
КонецФункции
...Показать Скрыть

Эффективно, когда происходит очень частое обращение к данным малого объема. Например, восстановление последовательности документов или пакетный ввод документов. Заодно приведу и стандартную функцию установки значения переменной с учетом "КЭШ"а. Примечание: НЕ путать эдакое название переменной с обычным кэшем базы 1С, который является кучей файлов на диске.
Процедура УстановитьЗначениеПеременной(ИмяПараметра, Кэш, ЗначениеПараметра, ОбновлятьВоВсехКэшах = Ложь) Экспорт
	
	Если Кэш = Неопределено Тогда
		// Кэш не был проинициализирован
		Кэш = Новый Структура;
	КонецЕсли;
	
	Если Кэш.Свойство(ИмяПараметра) Тогда
		Кэш.Вставить(ИмяПараметра, ЗначениеПараметра);
	КонецЕсли;
	
	#Если Клиент ИЛИ ВнешнееСоединение Тогда
		Если ОбновлятьВоВсехКэшах Тогда
			КэшНаСервере = ПараметрыСеанса.ОбщиеЗначения.Получить();
			КэшНаСервере.Вставить(ИмяПараметра, ЗначениеПараметра);
			ПараметрыСеанса.ОбщиеЗначения = Новый ХранилищеЗначения(КэшНаСервере);
		КонецЕсли;
	#КонецЕсли
	
КонецПроцедуры
...Показать Скрыть
5. Константин Юрин (kostyaomsk) 13.08.14 13:00
(3) pvase, обычно штрих-коды генерируют с целью записи в базу, так что тут только какая-то структура в ОЗУ на вкус разработчика (проиндексированная ТаблицаЗначений, Структура, соответствие. А вот при инициализации этого механизна и при окончании работы лучше всего ХранилищеЗначения. Ну еще может что-то быть для сохранения промежуточных результатов работы в базе.
6. Сергей Зверев (serferian) 13.08.14 13:48
(3) pvase,
Не согласен что так лучше. Все-таки Identity - 100% уникальность, а галочки используется и прочее - это во первых поиск "без галочки", а во-вторых блокировки "на запись данного значения"! Да и цифры "чисто теоретически" могут закончится)))
7. Сергей Зверев (serferian) 13.08.14 13:51
(4) kostyaomsk,
С константой вариант тоже не прошел - по причине блокировок! Пробовали!
С Кэшем интересно будет попробовать.



8. Константин Юрин (kostyaomsk) 13.08.14 21:02
Про уникальность по методу
Новый УникальныйИдентификатор;
с дальнейшей любой обработкой даже незначительно снижающей разрядность полученного числа.
Я представлял задачу по-другому:
1. Где-то сидят 20 операторов (по всему городу и набивают номенклатуру, а заодно и штрихкоды
2. В 1С из различных источников загружается что-попало из номенклатуры и все это надо "пометить" штрихкодами
3. Комбинация 1 и 2 + еще все что угодно + уже вышедшая из употребления (продажи, обработки) номенклатура
Причем, в длинных штрихкодах (не EAN13) нужно и тип товара проклассифицировать (весовой, штучный) и кучу "префиксов" предусмотреть (код региона, магазина, оператора). Еще и дополнительные штрих-коды могут быть для отличия своего товара от чужого (при попытке недобросовестного покупателя вернуть товар).
Причем, еще требование на сквозную нумерацию в пределах региона-магазина-оператора (было такое в практике по учету основных средств холдинга в консолидированной отчетности).
Это что касается "пометки".
А вот чтение должно осуществляться максимально быстро. Для этого по ночам регламентные задания должны перезаписывать блоками в порядке возрастания нужные штрихкода, и обновлять индексы для поиска по регионам-магазинам как для аппаратуры POS-терминала, так и для консолидированного товарного отчета.
Такой вот гипотетический пример, который все в себя включает.
Для поиска тут только оптимизированный запрос на SQL сервер. А вот для модификации: перед записью на компьютере "оператора штрихкодов" уже должны в ОЗУ быть прочитаны и его часть сложной структуры данных к которой должен быть доступ без блокировок с возможностью записи, т.к. тут предусмотрена нумерация в пределах сегмента.
Иначе как-то так:
Процедура ПолучитьНовыйШтрихкод(Регион, магазин, КодОператора, СписокНоменклатуры, ТабНом)    
    // ТабНом - таблица значений с колонками номенклатура и штрихкод
    Для Каждого Ном Из СписокНоменклатуры Цикл
       ЧастьШК = Лев(СтрЗаменить(ВРег(Новый УникальныйИдентификатор()),"-", ""), N); // уникальная часть ШК. 3 < N << 32
       НоваяСтрока = ТабНом.Добавить();
       НоваяСтрока.Номенклатура = Ном;
       НоваяСтрока.ШтрихКод = Регион + Магазин + КодОператора + ЧастьШК; 
    КонецЦикла;
    //... Что-то такое для сортировк и добавления индексов...
    ТабНом.Сортировать(...
КонецПроцедуры
...Показать Скрыть

От задачи зависит.
9. данила (danila_inf) 20.08.14 12:09
Задумка хороша. Вижу явное ограничение в следующем.
Запись идет во внешний источник данных.
Использование транзакции для сохранение целостности данных между 1с(таблицами sql связанные с 1с) и sql(независимая таблица от 1с) невозможно. При получении штрихкода
НовыйШтрихкод=Выборка.Fields(0).Value
транзакция в сиквел завершена, транзакция в 1с(если была начата) не завершена.
При аварийном завершении программы(возможны разные причины) счетчик будет увеличен только в сиквел.
Если задача по сквозной нумерации не стоит то задача решена на 5)).
10. Сергей Зверев (serferian) 20.08.14 12:27
(9) danila_inf,
При аварийных завершениях 1С - просто получаем пропуск данного ШК.
11. Константин Юрин (kostyaomsk) 27.08.14 16:41
Идея использования сервера SQL опять не по назначению (или сначала не по назначению для поиска максимального значения во внутренней таблице соответствующей на низком уровне регистру сведений 1С 8 путем вставки новой строки и последующего автоинкремента +1
Насчет скорости исполнения вопрос:
Функция ПолучитьНовыйШтрихкод(Организация, СтруктураПараметров) Экспорт
   ... 
   Соединение= Новый COMObject("ADODB.Connection"); // И вот это в цикле каждый раз!
   ...
КонецФункции;
...Показать Скрыть

И везде (раз подняли тему блокировок) SQL сервер атакую со всех сторон из 1С данная функция, каждый раз: создание COM-объекта, логин, пароль, авторизация, максимальный таймаут? А еще у комовских объектов бывает метод Close() не срабатывает.
А если нужно массив сгенерировать штрихкодов и записать. Вот то, что запрос через Опять цикл вызова функции ПолучитьНовыйШтрихкод?
А еще вызов самого метода COM-объекта? Для получения одного-единственного значения.
Мы в свое время использовали запросы 1С в SQL-серверном варианте вида:
ВЫБРАТЬ ПЕРВЫЕ 1
	Рег.Код КАК Код,
	Рег.СтрИнфо КАК СтрИнфо
ИЗ
	РегистрСведений.РегистрКодов КАК Рег

ДЛЯ ИЗМЕНЕНИЯ
	РегистрСведений.РегистрКодов

УПОРЯДОЧИТЬ ПО
	СтрИнфо УБЫВ,
	Рег УБЫВ
...Показать Скрыть

Если нужно записать за раз много данных, то получим последнее максимальное значение в нужной группе, а дальше генерация кодом таблицы значений для записи (примитивный алгоритм +1 с преобразованиями в строку) и дальше блокировка регистра и запись опять через 1С.
А в Вашем алгоритме получается полное отсутствие блокировок в момент записи строки что плюс, но вот сразу идет запись в базу нового штрихкода самим SQL-сервером. А если произойдет ошибка уже дальше в коде. Запрос то в транзакции записи и тот и другой уже отработал?
Или потом блок
Попытка-исключение
. Как сработает пропуск штрихкода?
12. Константин Юрин (kostyaomsk) 27.08.14 16:45
(9) danila_inf, более точно по-научному написал суть проблем. Хотелось бы узнать, можно ли как то усовершенствовать алгоритм чтоб, допустим сом-процесс был один и уже пусть на него идут запросы со всех сторон. Вернее будет хоть какая-то очередь транзакций со стороны 1С.
13. Константин Юрин (kostyaomsk) 27.08.14 20:47
Вот нашел в статье Спускаемся в 1С 8.2 на уровень Базы Данных (Часть2) подробно про доступ посредством СУБД MS SQL Server, и в частности про оптимизацию подключения:
...для оптимизации функцию получения объекта ADODB.Connection можно разместить в общем модуле, в настройках которого выставлено «Повторное использование». Это позволит не создавать каждый раз новый объект подключения, а будет использоваться уже созданный объект. В теории это позволит сократить время вызова соединения, а так же совсем чуть-чуть сэкономит ресурсы системы
.
14. mixsture 31.10.14 16:15
Мне кажется некорректным сравнение производительности между полноценным регистром сведений, который хранит явно больше информации с голой таблицей из цифр.

Регистр сведений:
-хранит еще ссылки на номенклатуру, характеристики, иногда серии. Плюс индексы по этому всему, которые ускоряют поиск, но замедляют запись (и, соответственно, блокировку)
-отлично работает с РИБ (да, у вас не получится с РИБ единой нумерации при одновременной работе пользователей в разных узлах, но сам обмен информацией работает на пять)
-Не стоит забывать о резервных копиях - сделав голую таблицу, вы добавили головной боли сисадмину - ему теперь все это нужно помнить и заодно делать бекап вместе с базой 1с. Опоздал на несколько минут - получай рассинхронизированную копию базы 1с и таблицы. А что делать, если нужно развернуть копию? Забыли про это - и она тоже будет увеличивать счетчик в таблице для боевой?
-В базе, возможно, куча подписок на события, сквозь которые тоже проходит выполнение при записи в этот регистр.

Наверно, для сравнения лучше подойдет объект попроще - константу, например, предлагали в комментариях. Или 1 элемент справочника - его блокировать, увеличивать реквизит на 1 и записывать.

Да и избыточная по времени блокировка как правило получается из-за того, что между получением значения и записью вставляют массу другой логики. Например, выбрали для изменения записи регистра, затем проводимся по кучи других регистров, масса проверок и вот в конце снимаем блокировку при фиксации транзакции. Так приближаем максимально чтение, запись и снятие блокировки - вот и нет их.
15. Сергей Зверев (serferian) 31.10.14 16:40
-отлично работает с РИБ (да, у вас не получится с РИБ единой нумерации при одновременной работе пользователей в разных узлах, но сам обмен информацией работает на пять)


угу еще один недостаток - когда идет обмен РС полностью уходит в блокировку!!! и спрашивается - зачем оно нам?
(правда этот механизм нужен только в центральной базе!)
ну и выход для РИБ тоже есть через префиксы - их никто не отменял.

-Не стоит забывать о резервных копиях - сделав голую таблицу, вы добавили головной боли сисадмину - ему теперь все это нужно помнить и заодно делать бекап вместе с базой 1с. Опоздал на несколько минут - получай рассинхронизированную копию базы 1с и таблицы. А что делать, если нужно развернуть копию? Забыли про это - и она тоже будет увеличивать счетчик в таблице для боевой?


тут уж как настроите подключение к БД - если копия - то данные о БДSQL можно получать из настроек БД 1C
а копии однозначно и давно создаются при помощи SQL Backup. вряд ли я смогу 300Gb выгрузкой - загрузко данных копировать)) - так что табличка в ажуре и актуальна!

Константа отпала сразу на основе начала реальной работы!
16. mixsture 02.11.14 22:02
(15) serferian, верно, поэтому я предлагаю использовать объект попроще, чем РС именно для задачи быстрого получения следующего номера. Пусть это будет 1 элемент справочника, в реквизите которого хранится последнее выданное число. Раз пропуски в номерах возможны => можно не связывать эту транзакцию с остальной логикой => читаем значение, делаем инкремент, записываем без пауз, а уже потом используем записанное значение. Я думаю, будет ненамного медленнее и особых блокировок не возникнет, зато все сделано средствами платформы и не добавляет мучений сисадминам, легко используется в остальных механизмах платформы.

тут уж как настроите подключение к БД - если копия - то данные о БДSQL можно получать из настроек БД 1C

Если вы хотите создавать таблицу внутри базы 1с - недостатков тоже порядочно: нарушаем лицензию 1с + конфигуратор легко может стереть нашу таблицу при загрузке.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа