Генерация последовательности в последовательности
Есть ли способ генерировать какой-то идентификатор порядка для записей таблицы?
Предположим, что у нас есть два потока, выполняющих запросы:
Тема 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
- самый слабый режим блокировки, который является самоисключительным - только один сеанс может удерживать его за раз.
Имейте в виду, что это не сделает эту последовательность безразличной - это другая проблема.