Имя: Пароль:
1C
1C 7.7
V7 SQL и гибкие блокировки.
0 Sserj
 
26.09.20
17:13
Вопросы к покрытым мхом зубрам клюшечникам :)
Так как пользователей перевалило за 200, вылизывать модули проведения уже некуда а ожидания завершения транзакций начали одолевать пришлось зарыться в тему гибких блокировок.
Разлочил _1SJOURN, _1SSYSTEM, _1SUIDCTL, справочники документы, регистры.
Уникальность ID обеспечил наборот блокировкой _1sp__1SJOURN_MaxID и _1sp__1SUIDCTL_GetMaxID хинтами UPDLOCK,ROWLOCK,HOLDLOCK. Так как эти хранимки вызываются в момент создания документа и там транзакция миллисекунды то это не критично. Для модулей проведения набросал класс, который создает софтовый лок (sp_getapplock) на измерения регистра по которому будут проведения, блокировки в идут всегда в алфавитном порядке, т.е. по идее вероятность дедлоков минимальная.
Но что так и не смог победить это обновление точки актуальности в _1SSYSTEM.
Т.е. сама 1С пытается выставить блокировку на _1SSYSTEM ДО ОбработкаПроведения а обновление ее проводит ПОСЛЕ ОбработкаПроведения. И конечно если _1SSYSTEM не залочена то пока идет проведение в нее уже понапишут другие документы, а если ее залочить то смысла во всем это нет никакого, так как опять все доки будут проводиться по очереди.
Вся беда в том что опять же ДО ОбработкаПроведения 1С делает запрос есть-ли какие-то еще документы между моментом ТА и моментом проводимого документа и начинает всегда ругаться на то что Существуют более ранние документы.
Сейчас это обошел просто наглым образом всегда при начале работы кидая ТА на начало завтрашнего дня (на конец сегодняшнего ничего хорошего, так как тогда всегда документы начинают спрашивать изменить время документа или нет).

Так вот собсно вопросы:
1. Можно ли как то подавить этот запрос перед ОбработкаПроведения на документы между ТА и текущим.
2. А что я еще мог не учесть. На тестовом сервере в 10 сеансах создаются потоком 10000 документов, кажется ошибок нет, ожиданий транзакций абсолютно никаких, но может есть что-то на скрытое?
1 МихаилМ
 
26.09.20
17:30
причем тут блокировки субд и гибкие блокировки  ?
2 Дык ё
 
26.09.20
17:35
(0) 1. повесь триггер на 1ssystem, который в немонопольном режиме не даст двигать ТА назад
2. счетчик NETCHGCN в 1susers

ну и блокировки в 1suidctl можно делать не табличные, а устанавливать sp_getapplock
3 Sserj
 
26.09.20
17:43
(2) Да счетчик NETCHGCN и так меняется самой 1С, я бы по хорошему бы его вообще всегда бы в ноль скидывал, он судя по всему вообще ни для чего кроме обновления списков документов/справочников не нужен. А у меня списки уже давно все ТабличноеПоле заменены.

1suidctl - тут не вижу необходимости на него блокировка ставится в момент вызова Новый() и всегда после _1sp__1SUIDCTL_TLock вызывается _1sp__1SUIDCTL_GetMaxID с кодом справочника или 0 для документов. Вот на этот _1sp__1SUIDCTL_GetMaxID я и поставил rowlock, т.е. для документов он блокирует последний id документов (он у них сквозной) и отдельно для каждого вида справочника.
4 Дык ё
 
27.09.20
12:47
(3) вопрос не в том, кто меняет NETCHGCN, а в том, что если одна транзакция его изменила, вторая при попытке его изменить будет висеть на блокировке до завершения первой
5 Sserj
 
27.09.20
13:32
(4) Не не будет. В этом собственно вся проблема и состоит. Обновление 1ssystem идет последней операцией перед коммитом. А запись в 1sjournal перед ОбработкаПроведения. И вот тут то и косяк что в это время другие документы уже видят что этот как будто бы проведен, но 1ssysteme еще нет данных о сдвиге ТА и они начинают ругаться что существую проведенные документы после ТА.
6 Sserj
 
27.09.20
15:58
(2) Ну вобщем пасибки за идею, триггер отлично справляется.
Повесил вот такой:
AFTER INSERT, UPDATE
AS
set nocount on
UPDATE [dbo].[_1SSYSTEM]
  set [CURTIME] = lastDoc.docTime, [EVENTIDTA] = lastDoc.docID
FROM
  [dbo].[_1SSYSTEM] as sysTable
  CROSS APPLY (
  select top 1
    cast(dbo.StrToID(SUBSTRING(DATE_TIME_IDDOC, 9, 6)) as int) as docTime
    , IDDOC as docID
  from
    [dbo].[_1SJOURN] with (nolock)
  where [DATE_TIME_IDDOC] between convert(char(8), sysTable.[CURDATE], 112) and (convert(char(8), sysTable.[CURDATE], 112) + 'ZZZ')
        and [CLOSED]&1 = 1
  order by [DATE_TIME_IDDOC] desc
  ) as lastDoc

При изменении всегда читает "грязно" и кидает ТА на последний проведенный дня.
Соответственно при ПриНачалеРаботыСистемы если монопольный режим удаляю его, если обычный то создаю.

Что же еще я мог упустить, вроде теперь все вообще отлично стало. Но как то срашно все это в реальную работу запускать :)
7 Злопчинский
 
27.09.20
17:18
Запускай, потом доложишь что получилось
8 Злопчинский
 
27.09.20
20:48
С таким количеством пользюков - это ты прям саблезубый тигр, к тебе за советами ходить надо..
9 Дык ё
 
27.09.20
23:41
(5) будет-будет. теоретики, блин. и при чем тут 1ssystem к netchgcn? это не связанные проблемы

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

(7) подстрекателей бьют первыми :)
10 Дык ё
 
27.09.20
23:45
(0) +ты конфу не указал. надеюсь, бух. компоненты у тебя нет, иначе твои приключения только начинаются
11 Дык ё
 
27.09.20
23:50
(3) на счёт 1suidctl - пока ты там зависишь от табличных блокировок, ты не сможешь в параллельных транзакциях проводить документы одного вида
12 Ёпрст
 
28.09.20
09:08
(3) если у тебя все списки заменены ТП, то как ты их обновляешь ? Если например, другой чорт что-то поменял в другом сеансе, как в текущем..стоит автообновление, или что ?
13 Ёпрст
 
28.09.20
09:09
+ сами записи в регистр, ты пихаешь "штатно" или через свой insert/update ?
14 Ёпрст
 
28.09.20
09:09
ну и итоги, соответственно, сам толкаешь, или 1с-ина ?
15 Ёпрст
 
28.09.20
09:10
и..есть же еще _1scrdoc..с ней что-то делал ? :)
16 Sserj
 
28.09.20
17:25
(9) Да не имеет ни тот ни другой к друг другу отношения. Ниже опишу проблему по картинке.
(10) Ну конечно используется только оперативный учет, все остальное давно на 8-ке. Когда то 20 лет назад она была ТиС-ом но сейчас это совсем другое :)
(11) Нет, при проведении 1suidctl вообще не изменяется. Он нужен ПриЗаписи да для всех документов iddoc сквозной и там же id для справочников, но операция записи быстрая, так что тут блокировка не напрягает.
Может это имеется ввиду нумерация из _1SDNLOCK она используется при Новый, но это у меня давным давно решено административно. Не помню уже зачем это было нужно, но каждый пользователь у нас имеет свой личный префикс всех номеров документов и кодов справочников.
(12) Ну да, это ТабличноеПоле из 1cpp там есть таймаут обновления. По сути у меня пользователи и не видят родных форм ни журналов ни документов ни справочников. Все формы на обработках а перед записью дергается VERSTAMP таблицы и говорится если кто-то что-то уже поменял, эдакая пародия на снеговика. А дальше административно кому-то можно перезаписывать, кто-то разрешения на что-то должен запросить, ну короче рутина :)
(13)(14) Да это штатно, так как у меня формы свои то мне в модулях проведения по сути никакие особые проверки не нужны. Пользователи не могут как то внезапно что-то провести.
Ну а контроль остатков тоже по восьмерошному после записи движений.
(15) А зачем с ней что-то делать?
В ней кодов нет, записи не пересекаются, т.е. как в момент Записать документа что-то занеслось, так оно там и хранится. При проведении к ней нет никаких обращений.

(9) Вернемся к обсуждению :)
Вот беру простенький документ у которого в модуле проведения нет ничего кроме SELECT 'ОбработкаПровдения', чтобы в профайлере видеть как вошли и вышли.
Делаю простенькую обработку:
док = СоздатьОбъект("Документ.Тестовый");
глЗапросODBC.ВыполнитьИнструкцию("SELECT 'ПЕРЕД НОВЫЙ'");
док.Новый();
ЗапросODBC.ВыполнитьИнструкцию("SELECT 'ПЕРЕД ЗАПИСАТЬ'");
док.Записать();
ЗапросODBC.ВыполнитьИнструкцию("SELECT 'ПЕРЕД ПРОВЕСТИ'");
док.Провести();
ЗапросODBC.ВыполнитьИнструкцию("SELECT 'ЗАВЕРШЕНО'");

Все на одном экране не помещается, поэтому две картинки
Новый и Запись:
https://ibb.co/DKq5zsS
Проведение:
https://ibb.co/W3wL6Q8

Новый: По идее номера в новом все равно не должны пересекаться, так как получение последнего и вставка в одной транзакции (строки 13, 14).
Записать: Вот тут блокировка у меня начинается со строки 26, потому как я изменил _1sp__1SUIDCTL_GetMaxID в оригинале она была с NOLOCK, я изменил на rowlock,holdlock, что блокирует от документов, но можно еще по одному справочнику каждого вида создавать. Но так как у меня нигде нет процедур формы ПриЗаписи это чистая вставка. Время по замерам настолько маленькое, что его можно не учитывать.

Провдение вот тут самое интересное.
Между вызовом Провести() и до попадания в ОбработкаПроведения() в строке 54 идет сравнение есть документы между ТА и текущим или нет.
Потом проваливаемся в проведение и после выхода из него сначала пишем в Journal а потом изменям данные в SYSTEM (строки 59, 61).
И вот тут если проведение тяжелое то уже другие документы успевают записать свое позднее время а наш документ опять перепишет его на свое старое. И получится что ТА сдвинулась назад.
Даже триггер на UPDATE System не помогает. Сегодня весь день гоняли и на тестах где-то в среднем на 5000 документов по попаданию было когда мжеду записью в журнал и обновлением SYSTEM успевали другие документы вклиниться и свое время записать.

Ну и пока пришел только к одному решению. Последней строкой при выходе из ОбработкаПроведения, ну конечно если проведение успешное, самому лочить SYSTEM. Тогда триггер всегда хорошо отрабатывает и точку ТА кидает на конец дня.
17 Дык ё
 
28.09.20
17:55
(16) "операция записи быстрая, так что тут блокировка не напрягает" - не важно, на сколько быстрая. измененная запись заблокирована до конца транзакции. в отличие от прикладных блокировок, которые можно снимать

"самому лочить SYSTEM" - нахрена? напиши нормальный триггер, который будет делать то, что я сказал, а не этот ужас из (6). и чем блокировка 1ssystem будет лучше блокировки 1sjourn в итоге?

в общем, удачи. она тебе понадобится :)
18 Sserj
 
28.09.20
18:27
(17) Нормальный триггер не дающий сдвигать ТА назад создает такую проблему что если последние документы удалить/отменить все начинают себя считать не оперативными.
Ну и соответственно остатки приходится не актуальные брать а считать.
А про NETCHGCN я честно говоря так и не понял. Он обновляется не в транзакциях. На картинках это строки 44 после записи и 68 после проведения.
19 Sserj
 
28.09.20
18:30
+(18) А блокировка 1ssystem будет у меня длиться всегда ровно 3 операции перед завершением транзакции. Т.е. она не мешает быстрым документам проводиться в течении пока тяжелый не выходит из ОбработкаПроведения.
20 Ёпрст
 
28.09.20
19:30
ну а

>>>Ну а контроль остатков тоже по восьмерошному после записи движ

хоть на прямом запрос ?
21 Ёпрст
 
28.09.20
19:32
(16) тест хоть на что ?

самый примитив, запусить в паре сеансов полное перепроведение всех доков, и в третьем просто попытаться ввести новый док и провести его
22 Ёпрст
 
28.09.20
21:34
И вот это можешь еще сделать, хотя бы
https://www.1cpp.ru/forum/YaBB.pl?num=1291788964
23 Sserj
 
29.09.20
15:09
(20) Если честно я даже и не помню уже черные запросы. Вот даже на форуме здесь иногда читаю и не могу понять как они написаны :)
(21) Примерное тестирование на нубуке:
https://ibb.co/tZ0St2t
8 сеансов, создание-проведение 400 документов документов в каждом, запуск по таймеру в одно время.
Больше сеансов у меня нубук начинает свопиться, причем его полностью SQL сервер утилизирует а 1С-ина теперь не долбится ожиданием транзакции а тихо ждет своей очереди.
(22) Да я читал это. Но такие тонкие тюнинги уже не сильно актуальны. Рабочий SQL у нас на 32-ух поточном железе стоит с 128ГБ памяти. Выжирает около 80Гб и становится тихим. Видимо все за чем обращаются уже в памяти держит неделями :)

Вобщем удалось это все в итоге победить следующими телодвиженями.
Сделал хранимку на сервере и вызываю ее последней строкой в ОбработкаПроведения при удачном завершении:
позиция = контекстДокумента.ПолучитьПозицию();
датаДок = Сред(позиция, 2, 8);
времяДок = СокрЛП(Сред(позиция, 10, 10));
идДок = _IdToStr(СокрЛП(Сред(позиция, 20)));
текстЗапроса = "exec [dbo].[_1sp__1SSYSTEM_ToLastDoc] '"+датаДок+"', "+времяДок+", '"+идДок+"'";

Позицию нужно разбирать из самого проводимого документа потому что ее еще не существует в базе данных, она будет туда записана после выхода из обработки проведения.
Сама процедурка:
PROCEDURE [dbo].[_1sp__1SSYSTEM_ToLastDoc] @CURDATE datetime, @CURTIME int, @EVENTIDTA char(9)
AS
set nocount on
UPDATE [dbo].[_1SSYSTEM]
set CURDATE = @CURDATE, CURTIME = @CURTIME, EVENTIDTA = @EVENTIDTA
where cast(CURDATE as date) <= @CURDATE

Вызывает обновление _1SSYSTEM только если ТА будет двигаться вперед.
Но на самом деле этот UPDATE не будет отрабатываться, так как триггер переделал с AFTER на INSЕAD:
TRIGGER _1SSYSTEM_Control
ON [dbo].[_1SSYSTEM]
INSTEAD OF UPDATE
AS
SET NOCOUNT ON
UPDATE [dbo].[_1SSYSTEM]
SET CURDATE = newVals.CURDATE
    , CURTIME = case when coalesce(lastDoc.docTime, newVals.CURTIME) > newVals.Curtime then coalesce(lastDoc.docTime, newVals.CURTIME) else newVals.CURTIME end
    , EVENTIDTA = case when coalesce(lastDoc.docTime, newVals.CURTIME) >= newVals.Curtime and coalesce(lastDoc.docID, newVals.EVENTIDTA) > newVals.EVENTIDTA
                       then coalesce(lastDoc.docID, newVals.EVENTIDTA)
                       else newVals.EVENTIDTA
                  end
    , DBSIGN = newVals.DBSIGN
    , DBSETUUID = newVals.DBSETUUID
    , SNAPSHPER = newVals.SNAPSHPER
    , ACCDATE = newVals.ACCDATE
    , FLAGS = newVals.FLAGS
FROM
  [dbo].[_1SSYSTEM] as sysTable
  cross join (select top 1 * from inserted) as newVals
  outer apply (
    select top 1
      cast(dbo.StrToID(SUBSTRING(DATE_TIME_IDDOC, 9, 6)) as int) as docTime
      , IDDOC as docID
    from  [dbo].[_1SJOURN] with (nolock)
    where [DATE_TIME_IDDOC] between convert(char(8), newVals.[CURDATE], 112) and (convert(char(8), newVals.[CURDATE], 112) + 'ZZZ')
          and [CLOSED]&1 = 1
    order by [DATE_TIME_IDDOC] desc
   ) as lastDoc

т.е. триггер всегда при обновлении подменяет позицию ТА на последний проведенный в дне или в случае вызывоа из последней строки проведения уже устанавливает ТА на ту позицию на которую только еще будет записан документ.
case..end в подстановке CURTIME и EVENTIDTA нужны в том случае если это первый документ на текущий день и lastDoc вернет NULL.

На тестах больше не ловили ошибок.
24 Ёпрст
 
29.09.20
18:57
(23) т.е у тя всегда проведение в потоке ? т.е та в одном дне со сдвигом позиции дока на та..?

в тесте, доки создаются за один день?
теста, где доки создаются(или перепроводятся) за прошлый месяц, не делал ?..ну там, где штатненько хотябы табличка итогов будет подольше итоги двигать и пересчитывать
25 Ёпрст
 
29.09.20
18:58
а лучше, перепровод с начала года..
26 Ёпрст
 
29.09.20
18:59
и..период хранения останков какой ? месяц поди, да ?
27 Злопчинский
 
29.09.20
22:22
я хренею с вашего мастерства! крутняк!
28 Ёпрст
 
29.09.20
22:25
(27) крутняк, это Садовников раньше выдавал, где вся движуха с записью и толканием регистров была на своих хранимках переписана. году эдак, в 2006 или раньше..
29 Ёпрст
 
29.09.20
22:26
у меня где-то валялся его код..искать надоть..и даже демка была
30 Sserj
 
30.09.20
06:50
(24) Ну не совсем день. 150 торговых точек в 5 часовых поясах, так что "день" это условное обозначение. Главное что поток новых документов идет постоянный. Бывают пики до 200 доков в минуту.
(25) Да делал конечно, ту картинку с тестом что выложил это просто мне уж циферки больно нравятся :).
А полный цикл тестов делается около получаса, там и несколько сеансов перепроводят весь год, несколько имитируют постоянную ошибку проведения/записи для аварийного выхода из транзакций, несколько сеансов "догоняют" друг друга, т.е. один делает документы, другой постоянно мониторит их и удаляет. На что фантазии только хватило и памяти с чем сталкивался.
(26) Да месяц, но если это с намеком о нулевом мусоре в остатках, то они чистятся каждый день регламентом.
Каждый делается обход всех регистров по алгоритму:
Заблокировали таблицу остатков
Заблокировали таблицу движений
Удалили текущие итоги
Рассчитали и записали ненулевые
Разблокировали
Уснули на 30 секунд, чтобы народ поработал
Пошли к следующему регистру.
Самый тяжелый регистр к концу месяца пересчитывается 3-4 секунды, так что пользователи только небольшое торможение видят в работе.

(28) Ну технически это не очень сложно, но у нас жесткое требование - экзотики по минимуму, чтобы можно было найти человека который сможет с этим работать в случае чего.
Мы даже работу dbf то перестали поддерживать только года 2-3 назад, а до этого в обязательном порядке все изменения тестировали с sqlite-ом.
31 Ёпрст
 
30.09.20
07:42
(30). Попробуй период 5 дней. Да будут таблички итогов побольше, и открывать период чаще ( надеюсь, его ты тоже см пямымзапросом открываешь). Зато запись доков и все отчеты быстрее, ибо итог близко, считать меньше.
32 Злопчинский
 
30.09.20
08:37
А имеет смысл за текущий "день" проводить по минимально необходимому (например, только по количеству), а все остальное досчитывать/допроводить регламентом"..? во время "простоя"..?
33 Ёпрст
 
30.09.20
08:52
(32) смысл ? Це не снеговик, в клюшках и так всё летает..
Есть два вида языков, одни постоянно ругают, а вторыми никто не пользуется.