Hello, world!Имеется таблица, упрощенный вид которой выглядит следующим образом:
serial _id - ID объекта
boolean _status - Статус объекта (true - используется, false - не используется)
* _object - Данные объекта.При подключении нового пользователя ему, на время работы, должен выделятся один из объектов. Сейчас это реализовано как 'SELECT _id ... WHERE _status=FALSE LIMIT 1;' -> 'UPDATE ... SET _status=TRUE WHERE _id=$i;'. Это бажная схема - она будет некорректно работать при одновременных запросах. Возможно ли как-то в один акт получить _id незанятого объекта и заблокировать его, так, чтобы конкурирующие запросы не мешали друг-другу? ПС. PostgreSQL 8.3.3.
UPDATE ... SET _status=TRUE WHERE _id=(SELECT _id ... WHERE _status=FALSE LIMIT 1);>[оверквотинг удален]
>
>Имеется таблица, упрощенный вид которой выглядит следующим образом:
> serial _id
> - ID объекта
> boolean _status - Статус объекта (true
>- используется, false - не используется)
> * _object
> - Данные объекта.
>
>При подключении нового пользователя ему, на время работы, должен выделятся один из объектов. Сейчас это реализовано как 'SELECT _id ... WHERE _status=FALSE LIMIT 1;' -> 'UPDATE ... SET _status=TRUE WHERE _id=$i;'. Это бажная схема - она будет некорректно работать при одновременных запросах. Возможно ли как-то в один акт получить _id незанятого объекта и заблокировать его, так, чтобы конкурирующие запросы не мешали друг-другу? ПС. PostgreSQL 8.3.3.
А _id заблокированного объекта теперь как получить?
>UPDATE ... SET _status=TRUE WHERE _id=(SELECT _id ... WHERE _status=FALSE LIMIT 1);Это та же самая бажная схема. Если 2 запроса начнут выполнятся одновременно, они выдадут 1 объект 2 раза.
Правильный вариант блокировать таблицу целиком или только строки выборки SELECT FOR UPDATE.
UPDATE RETURNING - позволяет и обновить и получить результат обновления.
Почитал про "SELECT FOR UPDATE" - не до конца разрбрался:
1) Нужно ли select и update делать в одной транзакцией? Можно привести пример (смотрел в гугле - все ссылки ведут на проблемы с savepoint при выполнении select for update - примеров пользования нет)?
2) Предположим, что заблокированная страка ожидает апдейта, - будет ли она выбираться при параллельном селекте?
Всё, разобрался экспериментальным путем - делать нужно в одной транзакции и таблица блокируется полностью до завершения транзакции. Блокировка таблицы - как-то не кашерно и может стать узким местом. Черт, неужели нет способа, чтобы не блокировать таблицу полностью?
>таблица
>блокируется полностью до завершения транзакции.Не правда, блокируются только строки из условия where до конца транзакции. если используется select for update. Хотя возможно блокировка целой таблицы будет лучше ))
>Блокировка таблицы - как-то не кашерно
>и может стать узким местом. Черт, неужели нет способа, чтобы не
>блокировать таблицу полностью?Вот именно, ты сериализуешь запросы, убивая параллельльность. Это и есть способ решения твоей проблемы с параллельностью ;)
>
>Не правда, блокируются только строки из условия where до конца транзакции. если
>используется select for update. Хотя возможно блокировка целой таблицы будет лучше
>))
>Я проверял - открыл две консоли - в одной набрал:
begin;
select t1 from test where t2=false for update;Потом во второй повторил ввод - на чем консоль повисла, и отвисала только по завершению транзакции в первой консоли.
> Вот именно, ты сериализуешь запросы, убивая параллельльность. Это и есть способ решения твоей проблемы с параллельностью ;)
Гы :) ничего не понял, можно поподробней! :)
>Я проверял - открыл две консоли - в одной набрал:
>begin;
>select t1 from test where t2=false for update;
>
>Потом во второй повторил ввод - на чем консоль повисла, и отвисала...блокируются только строки из условия where до конца транзакции. Ты блокировал одни и те же строки (одно условие where). Так и должно быть.
>> Вот именно, ты сериализуешь запросы, убивая параллельльность. Это и есть способ решения твоей проблемы с параллельностью ;)
>
>Гы :) ничего не понял, можно поподробней! :)Ты хочешь параллельно (конкурентно) раздавать id? Это не возможно. Блокирока, гаранирующая последовательное выполнение выдачи id будет иначе будет выдача одного id двум. Можно сделать её явной, можно неявной.
минимум блокировок (ожидания) - это открыть курсор со свободными id (не все, хватит 100 штук вполне) пытатся занять (update) их по одному. В большинстве случаев это будет первый полученный (fetch) id, редко второй и совсем редко дальнейшие.
Например сначала делаем select но без limit
дальше для каждого id пытаемся сделать UPDATE ... SET _status=TRUE WHERE _id=$i and _status=FALSE
Если обновление успешно, то именно этот id используем в дальнейшем.
;) действительно классно, но есть одно - база ~ 340Гб. К сожалению раньше не встречались задачи выходившие за пределы select/update/insert, а уважение к постгре большое. :) Поэтому хочется расковырять эту тему и найти родное (как надо) решение для постгри.
>;) действительно классно, но есть одно - база ~ 340Гб.Это не важно.
Можно сделать так.
UPDATE ... SET _status=TRUE WHERE _status=FALSE and _id= (select _id from ... limit 1)Но этот запрос может не сработать и тогда его надо повторять пока не сработает и пока есть свободные _id ;)
А можно открыть курсор и по одному перебирать fetch пока не получится сменить status.
А как-же предыдущий ответ?:
>UPDATE ... SET _status=TRUE WHERE _id=(SELECT _id ... WHERE _status=FALSE LIMIT 1);Это та же самая бажная схема. Если 2 запроса начнут выполнятся одновременно, они выдадут 1 объект 2 раза.
>А как-же предыдущий ответ?:
>Это та же самая бажная схема. Если 2 запроса начнут выполнятся одновременно,
>они выдадут 1 объект 2 раза.where _id=(select...) !!!AND!!! _status=False
Если её уже заблокировали, второе условие не даст это сделать ещё раз. Будет 0 rows updated.
В итоге получил:
UPDATE test SET t2=true WHERE t2=false AND t1=(SELECT t1 FROM test WHERE t2=false LIMIT 1) RETURNING t1;
Все правильно? ))
>В итоге получил:
>UPDATE test SET t2=true WHERE t2=false AND t1=(SELECT t1 FROM test WHERE
>t2=false LIMIT 1) RETURNING t1;
>
>
>Все правильно? ))Если обработаешь вариант когда этот запрос ничего тебе не вернёт - то правильно.
Почему не вернет?: '... RETURNING t1;'
>Почему не вернет?Потому что сначала работает select, а потом update. И между ними статус может поменятся.
BEGIN
LOCK TABLE ... IN ACCESS EXCLUSIVE MODE
SELECT _id ... WHERE _status=FALSE LIMIT 1
UPDATE ... SET _status=TRUE WHERE _id=$i
COMMIT