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

Столкнулся с проблемой блокировок/тормозов при назначении уникального Штрихкода.
Работало через поиск Макс. значения в Регистре сведений и записи туда нового значения.
В принципе данный функционал можно использовать для создания уникальныхзначений.
Только для Клиент-серверного режима работы. Только для 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(); 
        Выборка =»»
        Соединение = «»;

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

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

16 Comments

  1. yukon
    у нас используется Code 128

    Так можно вообще не заморачиваться с базой данных и т.п.

    Функция ПолучитьНовыйШтрихкод()
    Возврат СтрЗаменить(ВРег(Новый УникальныйИдентификатор()),»-«, «»);
    КонецФункции
    
    Reply
  2. serferian

    ага, только ручками очень тяжко потом это всё набирать в случае отказа сканера ШК

    Reply
  3. pvase

    (2) serferian,

    Если интересует без тормозов, то лучше создать массив уникальных значений а потом лишь их использовать. Не думаю что регистр сведений для этого лучшая реализация. Ведь требуется всего 3 поля, 1 — ID, 2 — значение, 3 — уже используется. Если вам подойдет на таблице SQL, то можете создать такую таблицу, заполнить ее значениями от 1 до 1000 000 000 и пользоваться ими.

    Reply
  4. jobkostya1c8
    Reply
  5. jobkostya1c8

    (3) pvase, обычно штрих-коды генерируют с целью записи в базу, так что тут только какая-то структура в ОЗУ на вкус разработчика (проиндексированная ТаблицаЗначений, Структура, соответствие. А вот при инициализации этого механизна и при окончании работы лучше всего ХранилищеЗначения. Ну еще может что-то быть для сохранения промежуточных результатов работы в базе.

    Reply
  6. serferian

    (3) pvase,

    Не согласен что так лучше. Все-таки Identity — 100% уникальность, а галочки используется и прочее — это во первых поиск «без галочки», а во-вторых блокировки «на запись данного значения»! Да и цифры «чисто теоретически» могут закончится)))

    Reply
  7. serferian

    (4) kostyaomsk,

    С константой вариант тоже не прошел — по причине блокировок! Пробовали!

    С Кэшем интересно будет попробовать.

    Reply
  8. jobkostya1c8

    Про уникальность по методу

    Новый УникальныйИдентификатор;

    с дальнейшей любой обработкой даже незначительно снижающей разрядность полученного числа.

    Я представлял задачу по-другому:

    1. Где-то сидят 20 операторов (по всему городу и набивают номенклатуру, а заодно и штрихкоды

    2. В 1С из различных источников загружается что-попало из номенклатуры и все это надо «пометить» штрихкодами

    3. Комбинация 1 и 2 + еще все что угодно + уже вышедшая из употребления (продажи, обработки) номенклатура

    Причем, в длинных штрихкодах (не EAN13) нужно и тип товара проклассифицировать (весовой, штучный) и кучу «префиксов» предусмотреть (код региона, магазина, оператора). Еще и дополнительные штрих-коды могут быть для отличия своего товара от чужого (при попытке недобросовестного покупателя вернуть товар).

    Причем, еще требование на сквозную нумерацию в пределах региона-магазина-оператора (было такое в практике по учету основных средств холдинга в консолидированной отчетности).

    Это что касается «пометки».

    А вот чтение должно осуществляться максимально быстро. Для этого по ночам регламентные задания должны перезаписывать блоками в порядке возрастания нужные штрихкода, и обновлять индексы для поиска по регионам-магазинам как для аппаратуры POS-терминала, так и для консолидированного товарного отчета.

    Такой вот гипотетический пример, который все в себя включает.

    Для поиска тут только оптимизированный запрос на SQL сервер. А вот для модификации: перед записью на компьютере «оператора штрихкодов» уже должны в ОЗУ быть прочитаны и его часть сложной структуры данных к которой должен быть доступ без блокировок с возможностью записи, т.к. тут предусмотрена нумерация в пределах сегмента.

    Иначе как-то так:

    Процедура ПолучитьНовыйШтрихкод(Регион, магазин, КодОператора, СписокНоменклатуры, ТабНом)
    // ТабНом — таблица значений с колонками номенклатура и штрихкод
    Для Каждого Ном Из СписокНоменклатуры Цикл
    ЧастьШК = Лев(СтрЗаменить(ВРег(Новый УникальныйИдентификатор()),»-«, «»), N); // уникальная часть ШК. 3 < N << 32
    НоваяСтрока = ТабНом.Добавить();
    НоваяСтрока.Номенклатура = Ном;
    НоваяСтрока.ШтрихКод = Регион + Магазин + КодОператора + ЧастьШК;
    КонецЦикла;
    //… Что-то такое для сортировк и добавления индексов…
    ТабНом.Сортировать(…
    КонецПроцедуры
    

    Показать

    От задачи зависит.

    Reply
  9. danila_inf

    Задумка хороша. Вижу явное ограничение в следующем.

    Запись идет во внешний источник данных.

    Использование транзакции для сохранение целостности данных между 1с(таблицами sql связанные с 1с) и sql(независимая таблица от 1с) невозможно. При получении штрихкода

    НовыйШтрихкод=Выборка.Fields(0).Value

    транзакция в сиквел завершена, транзакция в 1с(если была начата) не завершена.

    При аварийном завершении программы(возможны разные причины) счетчик будет увеличен только в сиквел.

    Если задача по сквозной нумерации не стоит то задача решена на 5)).

    Reply
  10. serferian

    (9) danila_inf,

    При аварийных завершениях 1С — просто получаем пропуск данного ШК.

    Reply
  11. jobkostya1c8

    Идея использования сервера SQL опять не по назначению (или сначала не по назначению для поиска максимального значения во внутренней таблице соответствующей на низком уровне регистру сведений 1С 8 путем вставки новой строки и последующего автоинкремента +1

    Насчет скорости исполнения вопрос:

    Функция ПолучитьНовыйШтрихкод(Организация, СтруктураПараметров) Экспорт
    …
    Соединение= Новый COMObject(«ADODB.Connection»); // И вот это в цикле каждый раз!
    …
    КонецФункции;
    

    И везде (раз подняли тему блокировок) SQL сервер атакую со всех сторон из 1С данная функция, каждый раз: создание COM-объекта, логин, пароль, авторизация, максимальный таймаут? А еще у комовских объектов бывает метод Close() не срабатывает.

    А если нужно массив сгенерировать штрихкодов и записать. Вот то, что запрос через Опять цикл вызова функции ПолучитьНовыйШтрихкод?

    А еще вызов самого метода COM-объекта? Для получения одного-единственного значения.

    Мы в свое время использовали запросы 1С в SQL-серверном варианте вида:

    ВЫБРАТЬ ПЕРВЫЕ 1
    Рег.Код КАК Код,
    Рег.СтрИнфо КАК СтрИнфо
    ИЗ
    РегистрСведений.РегистрКодов КАК Рег
    
    ДЛЯ ИЗМЕНЕНИЯ
    РегистрСведений.РегистрКодов
    
    УПОРЯДОЧИТЬ ПО
    СтрИнфо УБЫВ,
    Рег УБЫВ
    

    Показать

    Если нужно записать за раз много данных, то получим последнее максимальное значение в нужной группе, а дальше генерация кодом таблицы значений для записи (примитивный алгоритм +1 с преобразованиями в строку) и дальше блокировка регистра и запись опять через 1С.

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

    Или потом блок

    Попытка-исключение

    . Как сработает пропуск штрихкода?

    Reply
  12. jobkostya1c8

    (9) danila_inf, более точно по-научному написал суть проблем. Хотелось бы узнать, можно ли как то усовершенствовать алгоритм чтоб, допустим сом-процесс был один и уже пусть на него идут запросы со всех сторон. Вернее будет хоть какая-то очередь транзакций со стороны 1С.

    Reply
  13. jobkostya1c8

    Вот нашел в статье Спускаемся в 1С 8.2 на уровень Базы Данных (Часть2) подробно про доступ посредством СУБД MS SQL Server, и в частности про оптимизацию подключения:

    …для оптимизации функцию получения объекта ADODB.Connection можно разместить в общем модуле, в настройках которого выставлено «Повторное использование». Это позволит не создавать каждый раз новый объект подключения, а будет использоваться уже созданный объект. В теории это позволит сократить время вызова соединения, а так же совсем чуть-чуть сэкономит ресурсы системы

    .

    Reply
  14. mixsture

    Мне кажется некорректным сравнение производительности между полноценным регистром сведений, который хранит явно больше информации с голой таблицей из цифр.

    Регистр сведений:

    -хранит еще ссылки на номенклатуру, характеристики, иногда серии. Плюс индексы по этому всему, которые ускоряют поиск, но замедляют запись (и, соответственно, блокировку)

    -отлично работает с РИБ (да, у вас не получится с РИБ единой нумерации при одновременной работе пользователей в разных узлах, но сам обмен информацией работает на пять)

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

    -В базе, возможно, куча подписок на события, сквозь которые тоже проходит выполнение при записи в этот регистр.

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

    Да и избыточная по времени блокировка как правило получается из-за того, что между получением значения и записью вставляют массу другой логики. Например, выбрали для изменения записи регистра, затем проводимся по кучи других регистров, масса проверок и вот в конце снимаем блокировку при фиксации транзакции. Так приближаем максимально чтение, запись и снятие блокировки — вот и нет их.

    Reply
  15. serferian
    -отлично работает с РИБ (да, у вас не получится с РИБ единой нумерации при одновременной работе пользователей в разных узлах, но сам обмен информацией работает на пять)

    угу еще один недостаток — когда идет обмен РС полностью уходит в блокировку!!! и спрашивается — зачем оно нам?

    (правда этот механизм нужен только в центральной базе!)

    ну и выход для РИБ тоже есть через префиксы — их никто не отменял.

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

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

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

    Константа отпала сразу на основе начала реальной работы!

    Reply
  16. mixsture

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

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

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

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *