ничем не отличаются?
отличия проявляются только у пишущих
Тогда отличаются — технически, на RR не используются SIRead locks. К каким отличиям в их поведении это приводит — см. https://wiki.postgresql.org/wiki/SSI#Rollover Да, в PostgreSQL есть специальный режим BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;, в котором после того, как PostgreSQL гарантировал, что аномалии от этого невозможны, транзакции "внутри" работают точно так же, как на RR (не используют SIRead locks вообще).
я этот пример воспроизвёл на постгресе. Т1 упала при коммите…
Какой именно из обсуждавшихся? Или прямо тот, что по ссылке?
Лог двух сессий: /* Session1 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- T1 /* Session1 */ UPDATE rollover SET n = n + (SELECT n FROM rollover WHERE id = 2) WHERE id = 1; /* Session2 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- T2 /* Session2 */ UPDATE rollover SET n = n + 1 WHERE id = 2; /* Session2 */ COMMIT; /* Session2 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY; -- T3 SELECT count(*) FROM pg_class; /* Session1 */ COMMIT; /* Session2 */ SELECT n FROM rollover WHERE id IN (1, 2); -- Получаю: ERROR: could not serialize access due to read/write dependencies among transactions DETAIL: Reason code: Canceled on conflict out to pivot 11820632, during read. HINT: The transaction might succeed if retried. Всё 1:1 как написано — проверяйте, что Вы делаете не так.
1:1 всё так сделаю. скрины ж привёл
У Вас не видно последовательности и сессий на этих screenshots. Прямо скопируйте то, что я показал, в две сессии какого-то клиента (ну или копируйте в psql по одному), и выполните соответствующие запросы по одному, внимательно. > укажите свою версию 15.4. Но это обязано так работать (на последних minors), начиная с 9.1 — это простейший тест.
какая должна быть последовательность сессий? у меня обе сессии были открыты ещё до создания таблицы тестируемой а потом порядок транзакций точно такой же, как по ссылке версия у меня тоже PostgreSQL 15.4 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 12.2.1_git20220924-r10) 12.2.1 20220924, 64-bit
У меня же указана, какая — вот, указал для каждого запроса. > у меня обе сессии были открыты ещё до создания таблицы тестируемой Это не имеет значения — главное, чтобы открытых транзакций в них не было (и "забытых" паралелльных транзакций, в которых Вы это же тестировали — тоже, а то мало ли... ;) ). > а потом порядок транзакций точно такой же, как по ссылке Ну вот и посмотрите, как это должно работать (и у меня работает) при таком порядке. Да попробуйте ещё раз с самого начала (выполните DROP этой таблицы), что тут думать. ;)
ёёпт:) я сейчас удалю весь контейнер для эксперимента, в абсолютно пустой базе создам таблу и сделаю как в ссылке, посмотрим. upd: скачал, на всякий случай для справки указываю что получил WARNING: there is no transaction in progress то есть в исходный момент 0 транзакций в обеих сессиях
в общем, если хоть в Т1, хоть в Т2 после обновления прочитать эти данные, а потом закоммитить — падает именно Т1
у Т3 же снепшот берётся в момент выполнения select count(*) from pg_class; ?
WARNING: there is no transaction in progress Нет, стоп. ;) Этого Вы не должны видеть (это будет, если выдать COMMIT; или ROLLBACK; в сессию, где нет активной транзакции).
Ну так сам пример у Вас воспроизводится правильно, так?
> Этого Вы не должны видеть (это будет, если выдать COMMIT; или ROLLBACK; в сессию, где нет активной транзакции). ну я так и сделал. чтоб продемонстрировать что активных транзакций не было (вы выше предполагали что я мб чёт забыл закрыть)
да. но почему чтение после обновления всё ломает — мне непонятно моменты взятия снепшотов у всех трёх транзакций вроде бы остаются теми же, чтение ничего не меняет (казалось бы). неясно. в общем, в сообщении со скриншотами всё правильно просто это не 1 в 1 как по ссылке. отличается на лишний читающий запрос вот взглянув на эти скрины (последовательность тоже указал верно в сообщении) — можете ответить в чём дело, почему поведение меняется
> в общем, в сообщении со скриншотами всё правильно > просто это не 1 в 1 как по ссылке. Вот же ж... а я подумал, что у Вас не работает исходный пример. А Вы можете расписать то, что Вы делаете, аналогично тому, как писал я (со screenshots я перепечатывать не собираюсь, извините)?
вы можете не перепечатывать а просто в Т1 после апдейта добавить чтение всей таблице. и то же самое в Т2 тоже после апдейта 95% кода у вас уже есть (он взят из примера по ссылке)
``` /* Session1 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- T1 /* Session1 */ UPDATE rollover SET n = n + (SELECT n FROM rollover WHERE id = 2) WHERE id = 1; /* Session1 */ select * from rollover ; /* Session2 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- T2 /* Session2 */ UPDATE rollover SET n = n + 1 WHERE id = 2; /* Session2 */ select * from rollover ; /* Session2 */ COMMIT; /* Session2 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY; -- T3 /* Session2 */ SELECT count(*) FROM pg_class; /* Session1 */ COMMIT; /* Session1 */ ERROR: could not serialize access due to read/write dependencies among transactions DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt. HINT: The transaction might succeed if retried. ```
Во-первых, заметьте, что тут не нужна T3 (то же самое получается). Во-вторых... мы же уже обсуждали именно этот случай, или мне кажется? Ладно, как бы там ни было... /* Session1 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- T1 /* Session1 */ UPDATE rollover SET n = n + (SELECT n FROM rollover WHERE id = 2) WHERE id = 1; /* Session1 */ SELECT * FROM rollover; /* Session2 */ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- T2 /* Session2 */ UPDATE rollover SET n = n + 1 WHERE id = 2; /* Session2 */ SELECT * FROM rollover; /* Session2 */ COMMIT; /* Session1 */ COMMIT; Допустимые расписания (как и то, что каждая транзакция должна видеть при чтении после UPDATE): T1 T2 1. (1, 100), (2, 10) ----> (1, 110), (2, 10) ----> (1, 110), (2, 11) T2 T1 2. (1, 100), (2, 10) ----> (1, 100), (2, 11) ----> (1, 111), (2, 11) Так вот если разрешить выполнение по этому сценарию, то (несмотря на совпадение конечного результата с первым расписанием), транзакции будут видеть после своих update следующее: T1: (1, 110), (2, 10) T2: (1, 100), (2, 11) и ни одному из этих расписаний такое не соответствует — поэтому первую PostgreSQL откатывает.
то есть опять банальное раскладывание по возможным комбинациям очерёдности даёт все ответы…
Естественно, оно всегда даёт все ответы. :) Да, кстати, я вот в прошлый раз написал, что поиск цикла в графе предшествования — это просто shortcut. Но это не совсем так, т.е. это так только для того человека, который анализирует/проверяет сериализуемость — а алгоритмы обеспечения serializability как раз и строятся на предотвращении либо поиске (и разрыве, путём отката транзакций) таких циклов (или характерных для них структур, как это делает SSI) — потому что в несериализуемых расписаниях они неизбежно будут.
Обсуждают сегодня