<?xml version='1.0' encoding="utf-8"?>
      <rss version='2.0'>
      <channel>
      <title>Форум на Исходниках.RU</title>
      <link>https://forum.sources.ru</link>
      <description>Форум на Исходниках.RU</description>
      <generator>Форум на Исходниках.RU</generator>
  	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2266078</guid>
        <pubDate>Thu, 14 May 2009 17:50:24 +0000</pubDate>
        <title>[на редактирование] Последовательная нумерация</title>
        <link>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2266078</link>
        <description><![CDATA[Akina: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=272414&view=findpost&p=2266051'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>Romkin &#064; <time class="tag-quote__quoted-time" datetime="2009-05-14T21:17:49+04:00">14.05.09, 17:17</time></span><div class='quote '>Однако, где есть один документ без номера - там их станет много. У меня, допустим, наложено требование уникальности на номер, или сочетание (номер, год). Два документа с одинаковым номером или его отсутствием не пройдут. И отказываться неудобно: это отмена контроля и переложение его на оператора. Иногда это просто неприемлемо. <br>
Также документ &quot;без номера&quot; - а как на него ссылаться, в разговоре, например? Как на нечто, что было создано тогда-то?</div></div><br>
Я некогда - правда на другой СУБД, но не суть - реализовывал подобную схему.<br>
<br>
Что было: во-первых, два диапазона номеров - для чистовиков (то, что ты в конечном итоге получаешь) и для черновиков. Собственно документ, которому не присвоен номер постоянного учёта, и есть черновик. Заморачиваться я тогда не стал, номер был длинным знаковым целым, постоянные номера присваивались последовательными положительными зеначениями, а черновые - рандомными отрицательными. Дублирование номеров было исключено - заморачиваться на проверки я не стал, просто объявил его уникально индексированным и отлавливал ошибки (кстати, за три с лишним года ошибок дубблирования не помню ни одной). Это заодно решало и проблему ссылки на документ при совместной (не одновременной&#33;) работе с ним. И контроль на месте, и оператору никакой нагрузки. А от немеряного количества черновиков система была защищена процессом периодического обслуживания базы, при котором в числе прочих операций выполнялось удаление черновиков, последняя корректировка которых была более чем месяц назад (но и после этого их можно было без проблем вынуть из бекапа).<br>
<br>
Само собой я некоторые тонкости опускаю, некоторые адаптирую под обсуждаемую проблему - но по сути всё было именно так, как написано.]]></description>
        <author>Akina</author>
        <category>Базы данных FAQ</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2266051</guid>
        <pubDate>Thu, 14 May 2009 17:17:49 +0000</pubDate>
        <title>[на редактирование] Последовательная нумерация</title>
        <link>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2266051</link>
        <description><![CDATA[Romkin: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=272414&view=findpost&p=2266002'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>Akina &#064; <time class="tag-quote__quoted-time" datetime="2009-05-14T15:29:06+00:00">14.05.09, 15:29</time></span><div class='quote '>Накладок с уникальностью я, признаться, себе даже представить не могу, если процесс вычисления номера и обновления блокирует таблицу перед вычислением и ожидает отпускания при обнаружении блокирования другой транзакцией обновления номера. Атомарность присутствует - процесс занесения записи представляет собой один атом, а присвоения этой записи номера - другой атом. В случае же каких-то проблем с присвоением (получением номера) запись хоть и занесена, но всё ещё не валидна - очень удобно, кстати, бывает, сродни регистрации документа после длительного процесса его согласования - долго-долго он был просто бумажкой, с которой каждый делал что считает нужным, а потом в мгновение ока превратился в окончательный официальный документ.</div></div><br>
В принципе все верно. Однако, где есть один документ без номера - там их станет много. У меня, допустим, наложено требование уникальности на номер, или сочетание (номер, год). Два документа с одинаковым номером или его отсутствием не пройдут. И отказываться неудобно: это отмена контроля и переложение его на оператора. Иногда это просто неприемлемо. <br>
Также документ &quot;без номера&quot; - а как на него ссылаться, в разговоре, например? Как на нечто, что было создано тогда-то? <br>
Можно предложить кучу способов, но зачем усложнять? То, что я написал, я написал, видя, что проблемы есть. Если поможет - хорошо :)]]></description>
        <author>Romkin</author>
        <category>Базы данных FAQ</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2266002</guid>
        <pubDate>Thu, 14 May 2009 15:29:06 +0000</pubDate>
        <title>[на редактирование] Последовательная нумерация</title>
        <link>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2266002</link>
        <description><![CDATA[Akina: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=272414&view=findpost&p=2265882'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>Romkin &#064; <time class="tag-quote__quoted-time" datetime="2009-05-14T13:04:39+00:00">14.05.09, 13:04</time></span><div class='quote '>и тут иногда возможны накладки с уникальностью. Нумерация, к примеру, может начинаться каждый год заново, можно найти еще несколько условий, когда это просто неудобно или ненадежно. Структура обычно развивается, и лучше дать отдельный объект &quot;нумератор&quot;, чтобы не пришлось отслеживать изменения.<br>
Лучше уж обеспечить атомарность, тем более что это несложно.</div></div><br>
Накладок с уникальностью я, признаться, себе даже представить не могу, если процесс вычисления номера и обновления блокирует таблицу перед вычислением и ожидает отпускания при обнаружении блокирования другой транзакцией обновления номера. Атомарность присутствует - процесс занесения записи представляет собой один атом, а присвоения этой записи номера - другой атом. В случае же каких-то проблем с присвоением (получением номера) запись хоть и занесена, но всё ещё не валидна - очень удобно, кстати, бывает, сродни регистрации документа после длительного процесса его согласования - долго-долго он был просто бумажкой, с которой каждый делал что считает нужным, а потом в мгновение ока превратился в окончательный официальный документ.<br>
<br>
Кто будет во втором атоме считать номер - простой запрос или &quot;нумератор&quot;,- при этом совершенно фиолетово. Отдельный объект даже лучше, особенно если есть какие-то выпендроны... скажем, документ записан в базу 31.12 в 23.59, а номер ему затребован уже 01.01 в 00.02 - нумератор обнаруживает, что последний номер датирован предыдущим, а не текущим по часам сервера, годом, и начнёт новую нумерацию, выдав номер 0 (или 1).]]></description>
        <author>Akina</author>
        <category>Базы данных FAQ</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2265882</guid>
        <pubDate>Thu, 14 May 2009 13:04:39 +0000</pubDate>
        <title>[на редактирование] Последовательная нумерация</title>
        <link>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2265882</link>
        <description><![CDATA[Romkin: Теоретически все точки отката пройдены, когда сделан commit :)<br>Практически - да, можно вставить запись, а потом сделать апдейт, присвоив номер. Но и тут иногда возможны накладки с уникальностью. Нумерация, к примеру, может начинаться каждый год заново, можно найти еще несколько условий, когда это просто неудобно или ненадежно. Структура обычно развивается, и лучше дать отдельный объект &quot;нумератор&quot;, чтобы не пришлось отслеживать изменения.<br>Лучше уж обеспечить атомарность, тем более что это несложно.]]></description>
        <author>Romkin</author>
        <category>Базы данных FAQ</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2265871</guid>
        <pubDate>Thu, 14 May 2009 12:51:34 +0000</pubDate>
        <title>[на редактирование] Последовательная нумерация</title>
        <link>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2265871</link>
        <description><![CDATA[Akina: Я не понимаю - а почему нельзя сначала выполнить проверки, и при не-прохождении откатить, а при прохождении - присвоить номер? то есть номер присваивается не сразу, а только после того, как пройдены все возможные точки отката транзакции...]]></description>
        <author>Akina</author>
        <category>Базы данных FAQ</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2265828</guid>
        <pubDate>Thu, 14 May 2009 12:23:22 +0000</pubDate>
        <title>[на редактирование] Последовательная нумерация</title>
        <link>https://forum.sources.ru/index.php?showtopic=272414&amp;view=findpost&amp;p=2265828</link>
        <description><![CDATA[Romkin: В базах данных, имеющих отношение к делопроизводству, часто возникает потребность в последовательной генерации номеров документов. При этом существует условие, что номера документам присваиваются последовательно, и есть требование использовать все номера. То есть, если существуют документы с №3 и №5, то существует (возможно, существовал и был удален) документ с №4.<br>
Задача не так проста, как кажется. Точнее, она была бы простой, если бы не было откатов транзакций при неудачно составленном документе.<br>
Типичная процедура присвоения номера делается так:<br>
Пользователь создает новый документ, и заполняет его. На этом этапе номер еще не присваивается. После заполнения пользователь нажимает &quot;ОК&quot;, и документ отправляется в таблицы БД. Открывается транзакция, получается номер, и вся структура документа вставляется в таблицы, с необходимыми проверками. Транзакция подтверждается. <br>
Если же документ проверку не прошел, транзакция откатывается. В этом случае полученный уже номер надо бы вернуть. Вот тут и возникают трудности.<br>
Дело в том, что Firebird, равно как и Interbase, версионники. При этом всегда надо рассчитывать, что вставки документов в базу могут идти одновременно от нескольких пользователей, и как взять номер, допустим, из таблицы, так чтобы другие вставки документов не взяли его же, понять не так просто.<br>
Недавно я познакомился с оригинальным способом такой нумерации: <br>
1. Создается генератор, например, GEN_NEW_ID<br>
2. При вставке документа из генератора получают новое значение, GEN_ID(GEN_NEW_ID, 1);<br>
3. Если при вставке документа возникло исключение, транзакция откатывается. При этом отдельно откатывается и генератор, GEN_ID(GEN_NEW_ID, -1);<br>
То есть, генератор выдает, например, номер 3, он присваивается документу, если не прошло - генератор откатывается, чтобы в следующий раз снова выдать этот номер.<br>
Все. Поскольку откаты документа довольно редки, а пользователи создают документы довольно медленно, минут 15-20, то все вроде бы в порядке.<br>
Однако все дело в том, что делать так нельзя. Как раз генератор находится вне контекста транзакции, а на вставку документа уходит какое-то время, пусть и маленькое. Это значит, что операция получения номера, в отличие от операци вставки документа, не атомарна. Это означает, что есть проблемы :)<br>
Допустим, два пользователя почти одновременно закончили оформление документов и нажали ОК. Открылась первая транзакция, из генератора документ получил номер, допустим, 3. Тут же за ней второй документ в своей транзакции получил номер 4, все логично. И тут документ №3 не проходит проверку. А второй, №4 - проходит. По алгоритму, программа, пытавшаяся вставить первый документ, откатывает генератор на 1...<br>
В результате документа №3 нет, зато есть №4, и следующий тоже будет №4. Который, скорее всего, откатится с ошибкой вида &quot;номер не уникален&quot;, опять установив генератор на единицу меньше. И так далее :)<br>
<br>
Между тем, существует довольно много способов сделать такую нумерацию в Firebird (и Interbase), и не заложить подобных &quot;мин замедленного действия&quot;. Я знаю парочку. <br>
<br>
Первый способ. Понятно, что нужно что-то вроде генератора, но в контексте транзакции, чтобы можно было откатить значение, и одновременно не дать доступ другим, пока не решится, утверждена транзакция или нет.<br>
Делается таблица <div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">create table DOC_NUM (</div><div class="code_line">&nbsp;&nbsp;DOC_ID integer not null primary key)</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script><br>
В нее вставляется одна запись, со значением 0. Это и есть наш генератор. Остается только наложить блокировку, и можно делать <div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">update DOC_NUM set DOC_ID = DOC_ID + 1;</div></ol></div></div></div></div>, а затем брать новый номер. Если вставка документа провалилась, надо просто откатить транзакцию.<br>
Обращение к этой таблице надо делать из бокирующей транзакции, с ожиданием, например, с параметрами<br>
write<br>
consistency<br>
lock_read=DOC_NUM<br>
lock_write=DOC_NUM<br>
exclusive <br>
Все, вторая транзакция просто будет ждать, пока первая не утвердится или откатится. По умолчанию wait_timeout 10 секунд, если мне не изменяет память, и этого должно быть вполне достаточно, чтобы документ прошел все проверки. Подробнее о параметрах <a class='tag-url' href='http://ibase.ru/devinfo/ibtrans.htm' target='_blank'>http://ibase.ru/devinfo/ibtrans.htm</a><br>
<br>
Второй способ.<br>
Первый способ хорош для документов, у которых долгое оформление и быстрая вставка. Если же вставки идут часто, а проверка долгая, то может организоваться очередь, в которой последний документ просто не успеет дождаться номера. Это теоретически, я не уверен, что такое бывает. В принципе, особой проблемы нет: пользователь в крайнем случае проведет документ еще раз.<br>
Но есть возможность избежать очереди, просто немного модифицировав таблицу:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">CREATE GENERATOR GEN_DOC_NUM_ID;</div><div class="code_line">&nbsp;</div><div class="code_line">CREATE TABLE DOC_NUM (</div><div class="code_line">&nbsp;&nbsp; &nbsp;DOC_ID &nbsp;INTEGER NOT NULL</div><div class="code_line">);</div><div class="code_line">&nbsp;</div><div class="code_line">ALTER TABLE DOC_NUM ADD CONSTRAINT PK_DOC_NUM PRIMARY KEY (DOC_ID);</div><div class="code_line">&nbsp;</div><div class="code_line">SET TERM ^ ;</div><div class="code_line">&nbsp;</div><div class="code_line">CREATE TRIGGER DOC_NUM_AUTOINC FOR DOC_NUM</div><div class="code_line">BEFORE INSERT </div><div class="code_line">AS</div><div class="code_line">BEGIN</div><div class="code_line">&nbsp;&nbsp;IF (NEW.DOC_ID IS NULL) THEN</div><div class="code_line">&nbsp;&nbsp; &nbsp;NEW.DOC_ID = GEN_ID(GEN_DOC_NUM_ID, 1);</div><div class="code_line">END</div><div class="code_line">^</div><div class="code_line">&nbsp;</div><div class="code_line">SET TERM ; ^</div></ol></div></div></div></div><br>
То есть, поле DOC_ID теперь не только первичный ключ, но и автоинкремент.<br>
Алгоритм такой:<br>
1. Непосредственно перед вставкой документа надо вставить запись в эту таблицу:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">insert into DOC_NUM (DOC_ID)</div><div class="code_line">values (NULL);</div></ol></div></div></div></div><br>
Теперь в ней есть хотя бы один номер, причем не обязательно тот, что нужен. Транзакцию надо подтвердить, и перейти собственно к вставке документа:<br>
2. Получение очередного номера делается из процедуры:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">SET TERM ^ ;</div><div class="code_line">&nbsp;</div><div class="code_line">CREATE PROCEDURE GET_DOC_NUM</div><div class="code_line">returns (NEW_DOC_NUM integer)</div><div class="code_line">AS</div><div class="code_line">begin</div><div class="code_line">&nbsp;&nbsp;for select DOC_ID</div><div class="code_line">&nbsp;&nbsp; &nbsp;from DOC_NUM</div><div class="code_line">&nbsp;&nbsp; &nbsp;order by DOC_ID</div><div class="code_line">&nbsp;&nbsp; &nbsp;into :NEW_DOC_NUM</div><div class="code_line">&nbsp;&nbsp; &nbsp;as cursor NUM</div><div class="code_line">&nbsp;&nbsp;do begin</div><div class="code_line">&nbsp;&nbsp; &nbsp;delete from DOC_NUM</div><div class="code_line">&nbsp;&nbsp; &nbsp;where current of NUM;</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;suspend;</div><div class="code_line">&nbsp;&nbsp; &nbsp;exit;</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;when GDSCODE lock_conflict DO</div><div class="code_line">&nbsp;&nbsp; &nbsp;begin</div><div class="code_line">&nbsp;&nbsp; &nbsp;end</div><div class="code_line">&nbsp;&nbsp;end</div><div class="code_line">end^</div><div class="code_line">&nbsp;</div><div class="code_line">SET TERM ; ^</div></ol></div></div></div></div><br>
Процедуру надо бы выполнять из обычной транзакции READ COMMITTED:<br>
read_committed<br>
rec_version<br>
nowait<br>
Все замечательно проходит. Здесь смысл в том, что при изменении записи изменение ее другими транзакциями блокируются. <br>
Курсор в процедуре идет последовательно от самого меньшего номера к самому большему, и пытается их удалить. Если этот номер удален - значит, другая транзакция успела раньше. Конфликт перехватывается и игнорируется, курсор переходит к следующей записи, и пытается удалить уже ее. Если получилось - все прерывается, и возвращается как раз удаленный номер.<br>
Поскольку каждая операция вставки документа сначала точно вставляет запись в таблицу, то записей должно хватить всем.<br>
Если вставка документа откатится, то откатится и операция удаления. И номер снова появится в таблице.<br>
Для надежности можно записать перехват как <div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">when GDSCODE lock_conflict,</div><div class="code_line">&nbsp;&nbsp; &nbsp; GDSCODE lock_timeout,</div><div class="code_line">&nbsp;&nbsp; &nbsp; GDSCODE deadlock DO</div></ol></div></div></div></div><br>
Недостатком данного способа является то, что количество записей в DOC_NUM растет с каждым откатом документа. Что, впрочем, косвенно показывает, сколько документов не получилось вставить.<br>
Хотя второй способ я на практике не испытывал, думаю, он имеет право на существование.]]></description>
        <author>Romkin</author>
        <category>Базы данных FAQ</category>
      </item>
	
      </channel>
      </rss>
	