Генерация последовательности в последовательности

Есть ли способ генерировать какой-то идентификатор порядка для записей таблицы?

Предположим, что у нас есть два потока, выполняющих запросы:

Тема 1:

begin;
insert into table1(id, value) values (nextval('table1_seq'), 'hello');
commit;

Тема 2:

begin;
insert into table1(id, value) values (nextval('table1_seq'), 'world');
commit;

Вполне возможно (в зависимости от времени), что внешний наблюдатель увидит запись (2, "мир" ) перед (1, "привет" ).

Это хорошо, но я хочу, чтобы все записи в "таблице1" появились с момента последнего наблюдения внешнего наблюдателя.

Итак, есть ли способ получить записи в том порядке, в котором они были вставлены? Может быть, OID могут помочь?

Ответ 1

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

Ну, есть Postgres конкретные системные столбцы cmin и ctid, которые вы могли бы злоупотреблять в некоторой степени.

Идентификатор кортежа (ctid) содержит номер блока файла и позицию в блоке для строки. Таким образом, это представляет собой текущий физический порядок на диске. Более поздние дополнения будут иметь больше ctid, как правило. Ваш оператор SELECT может выглядеть следующим образом:

SELECT *, ctid   -- save ctid from last row in last_ctid
FROM   tbl
WHERE  ctid > last_ctid
ORDER  BY ctid

ctid имеет тип данных tid. Пример: '(0,9)'::tid

Однако он является нестабильным как долгосрочный идентификатор, поскольку VACUUM или любой параллельный UPDATE или некоторые другие операции могут изменить физическое местоположение кортежа в любое время. Тем не менее, в течение транзакции он стабилен. И если вы просто вставляете и ничего больше, он должен работать локально для вашей цели.

Я бы добавил столбец timestamp по умолчанию now() в дополнение к столбцу serial...

Я также позволю столбцу по умолчанию заполнить столбец id (a serial или IDENTITY column). Это возвращает номер из последовательности на более поздней стадии, чем явно загружает, а затем вставляет его, тем самым сводя к минимуму (но не устраняя) окно для условия гонки - вероятность того, что более низкий id будет вставлен позднее. Подробные инструкции:

Ответ 2

То, что вы хотите, - заставить транзакции совершать (делая их вставки видимыми) в том же порядке, в котором они вставляли. Что касается других клиентов, то вставок не произошло, пока они не были зафиксированы, так как они могут отбросить назад и исчезнуть.

Это верно, даже если вы не вставляете вставки в явном begin/commit. Транзакция совершает, даже если она выполняется неявно, по-прежнему не обязательно выполняется в том же порядке, в котором была вставлена ​​его строка. Он подчиняется решениям о планировщике системного планировщика и т.д.

Даже если PostgreSQL поддерживает грязные чтения, это все равно будет правдой. Просто потому, что вы запускаете три вставки в заданном порядке, это не значит, что они закончат в этом порядке.

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

Вы можете использовать консультативную блокировку, но эффект тот же.

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

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

Я думаю, вам следует отступить и посмотреть, почему у вас есть это требование, и, учитывая это требование, почему вы используете отдельные параллельные вставки.

Может быть, вам будет лучше делать мелкие блочные вставки с одного сеанса?

Ответ 3

Если вы имеете в виду, что каждый запрос, если он видит строку world, должен также видеть строку hello, тогда вам нужно будет сделать:

begin;
lock table table1 in share update exclusive mode;
insert into table1(id, value) values (nextval('table1_seq'), 'hello');
commit;

Этот share update exclusive mode - самый слабый режим блокировки, который является самоисключительным - только один сеанс может удерживать его за раз.

Имейте в виду, что это не сделает эту последовательность безразличной - это другая проблема.