Выберите разблокированную строку в Postgresql - PullRequest
41 голосов
/ 23 декабря 2008

Есть ли способ выбрать строки в Postgresql, которые не заблокированы? У меня есть многопоточное приложение, которое будет делать:

Select... order by id desc limit 1 for update

на столе.

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

Один получает блокировку строки, другой блокирует, а затем отказывает после того, как первый обновляет строку. Что мне действительно нужно, так это чтобы второй поток получил первую строку, которая соответствует предложению WHERE и еще не заблокирована.

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

Таким образом, если есть строки с ID: 1,2,3,4, вступит первый поток, выберите строку с ID=4 и немедленно обновите ее.

Если во время этой транзакции приходит второй поток, я бы хотел получить строку с ID=3 и немедленно обновить эту строку.

Поскольку Share не выполнит этого ни с nowait, так как предложение WHERE будет соответствовать заблокированной строке (ID=4 in my example). В основном я хотел бы что-то вроде «И НЕ БЛОКИРОВАТЬ» в предложении WHERE.

Users

-----------------------------------------
ID        | Name       |      flags
-----------------------------------------
1         |  bob       |        0
2         |  fred      |        1
3         |  tom       |        0
4         |  ed        |        0

Если запрос "Select ID from users where flags = 0 order by ID desc limit 1" и когда возвращается строка, следующая вещь "Update Users set flags = 1 where ID = 0", тогда я бы хотел, чтобы первый поток захватил строку с ID 4, а следующий - для возьмите строку с ID 3.

Если я добавляю «For Update» к выбору, то первый поток получает строку, второй блокирует, а затем ничего не возвращает, потому что как только первая транзакция фиксирует, предложение WHERE больше не выполняется.

Если я не использую "For Update", тогда мне нужно добавить предложение WHERE при последующем обновлении (WHERE flags = 0), чтобы только один поток мог обновить строку.

Второй поток выберет ту же строку, что и первый, но обновление второго потока завершится неудачно.

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

Ответы [ 14 ]

0 голосов
/ 24 декабря 2008

Как насчет следующего? К можно относиться более атомарно, чем к другим примерам, но следует все же проверить, чтобы убедиться, что мои предположения не ошибочны.

UPDATE users SET flags = 1 WHERE id = ( SELECT id FROM users WHERE flags = 0 ORDER BY id DESC LIMIT 1 ) RETURNING ...;

Скорее всего, вы все равно будете застревать с любой схемой блокировки, используемой postgres для обеспечения согласованных результатов SELECT при одновременном обновлении.

0 голосов
/ 23 декабря 2008

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

0 голосов
/ 23 декабря 2008

Чего ты пытаешься достичь? Можете ли вы лучше объяснить, почему ни разблокированные обновления строк, ни полные транзакции не будут делать то, что вы хотите?

Еще лучше, можете ли вы предотвратить конфликты и просто использовать каждый поток по-разному? Это не будет работать хорошо, если соответствующая часть таблицы часто обновляется; у вас все еще будут столкновения, но только во время большой нагрузки при вставке.

Select... order by id desc offset THREAD_NUMBER limit 1 for update
0 голосов
/ 23 декабря 2008

Похоже, вы ищете SELECT FOR SHARE.

http://www.postgresql.org/docs/8.3/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE

FOR SHARE ведет себя аналогично, за исключением того, что он получает общую, а не монопольную блокировку для каждой извлеченной строки. Совместная блокировка блокирует выполнение другими транзакциями UPDATE, DELETE или SELECT FOR UPDATE для этих строк, но не мешает им выполнять SELECT FOR SHARE.

Если конкретные таблицы названы в FOR UPDATE или FOR SHARE, то блокируются только строки из этих таблиц; любые другие таблицы, используемые в SELECT, просто читаются как обычно. Предложение FOR UPDATE или FOR SHARE без списка таблиц влияет на все таблицы, используемые в команде. Если для представления или подзапроса применяется FOR UPDATE или FOR SHARE, это влияет на все таблицы, используемые в представлении или подзапросе.

Может быть записано несколько предложений FOR UPDATE и FOR SHARE, если необходимо указать различное поведение блокировки для разных таблиц. Если одна и та же таблица упоминается (или неявно затрагивается) предложениями FOR UPDATE и FOR SHARE, то она обрабатывается как FOR UPDATE. Аналогично, таблица обрабатывается как NOWAIT, если это указано в любом из пунктов, влияющих на нее.

FOR UPDATE и FOR SHARE нельзя использовать в тех случаях, когда возвращаемые строки нельзя четко идентифицировать с отдельными строками таблицы; например, их нельзя использовать с агрегацией.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...