Выберите разблокированную строку в Postgresql

Вот полное решение, использующее подход Collections.shuffle:

public static void shuffleArray(int[] array) {
  List<Integer> list = new ArrayList<>();
  for (int i : array) {
    list.add(i);
  }

  Collections.shuffle(list);

  for (int i = 0; i < list.size(); i++) {
    array[i] = list.get(i);
  }    
}

Обратите внимание, что он страдает из-за невозможности плавного перехода между int[] и Integer[] (и, следовательно, int[] и List<Integer>).

42
задан Kent Fredric 24 December 2008 в 08:15
поделиться

9 ответов

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

А вероятное решение состоит в том, чтобы добавить где пункт, ограничивающий его необработанными запросами:

select * from queue where flag=0 order by id desc for update;
update queue set flag=1 where id=:id;
--if you really want the lock:
select * from queue where id=:id for update;
...

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

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

В зависимости от природы Вашего приложения, могут быть лучшие способы реализовать это, чем в базе данных, такой как канал LIFO или FIFO. Кроме того, может быть возможно инвертировать порядок, что Вам нужны они в, и использовать последовательность, чтобы гарантировать, что они обрабатываются последовательно.

2
ответ дан Grant Johnson 23 September 2019 в 14:14
поделиться

Это может быть выполнено ВЫБОРОМ... NOWAIT; пример здесь .

1
ответ дан 23 September 2019 в 14:14
поделиться

Похож Вы ищете ВЫБОР ДЛЯ ДОЛИ.

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

ДЛЯ ДОЛИ ведет себя точно так же за исключением того, что она получает общую, а не монопольную блокировку на каждой полученной строке. Коллективная блокировка блокирует другие транзакции от работающего ОБНОВЛЕНИЯ, УДАЛИТЕ или ВЫБЕРИТЕ FOR UPDATE на этих строках, но это не препятствует тому, чтобы они выполнили ВЫБОР ДЛЯ ДОЛИ.

, Если определенные таблицы называют в FOR UPDATE или ДЛЯ ДОЛИ, то только строки, прибывающие из тех таблиц, заблокированы; любые другие таблицы, используемые в ВЫБОРЕ, просто читаются, как обычно. FOR UPDATE или ДЛЯ пункта ДОЛИ без списка таблицы влияет на все таблицы, используемые в команде. Если FOR UPDATE или ДЛЯ ДОЛИ применяется к представлению или подзапросу, это влияет на все таблицы, используемые в представлении или подзапросе.

Несколько FOR UPDATE и ДЛЯ пунктов ДОЛИ может быть записан, если необходимо определить различное поведение при блокировании для различных таблиц. Если та же таблица упомянута (или неявно затронута), и FOR UPDATE и ДЛЯ пунктов ДОЛИ, то это обрабатывается что касается ОБНОВЛЕНИЯ. Точно так же таблица обрабатывается как NOWAIT, если это определяется в каком-либо из пунктов, влияющих на него.

FOR UPDATE и ДЛЯ ДОЛИ не может использоваться в контекстах, где возвращенные строки не могут быть ясно отождествлены с отдельными строками таблицы; например, они не могут использоваться с агрегированием.

0
ответ дан Steven Behnke 23 September 2019 в 14:14
поделиться

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

0
ответ дан alanc10n 23 September 2019 в 14:14
поделиться

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

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

Select... order by id desc offset THREAD_NUMBER limit 1 for update
0
ответ дан HUAGHAGUAH 23 September 2019 в 14:14
поделиться

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

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

Вы, вероятно, все еще застрянете с любым использованием пост-ГРЭС схемы блокировки внутренне для предоставления последовательных ИЗБРАННЫХ результатов перед лицом одновременные ОБНОВЛЕНИЯ.

0
ответ дан HUAGHAGUAH 23 September 2019 в 14:14
поделиться

Я столкнулся с той же проблемой в нашем приложении и пришел с решением, которое очень похоже на подход Гранта Джонсона. Канал FIFO или LIFO не подходил, потому что у нас есть кластер серверов приложений, обращающихся к одной БД. Мы делаем

SELECT ... WHERE FLAG=0 ... FOR UPDATE
, за которым сразу следует
UPDATE ... SET FLAG=1 WHERE ID=:id
как можно скорее, чтобы время блокировки было как можно меньшим. В зависимости от количества и размеров столбцов таблицы может оказаться полезным получение идентификатора только при первом выборе и после того, как вы отметили строку, чтобы получить оставшиеся данные. Хранимая процедура может еще больше сократить количество циклов обмена.
0
ответ дан 26 November 2019 в 23:57
поделиться

^^, что работает. подумайте о том, чтобы иметь "немедленный" статус "заблокирован".

Допустим, ваша таблица выглядит так:

id | имя | фамилия | статус

И возможные статусы, например: 1 = ожидание, 2 = заблокировано, 3 = обработано, 4 = сбой, 5 = отклонено

Каждая новая запись вставляется с ожидающим статусом (1)

Ваша программа выполняет : "update mytable set status = 2 where id = (select id from mytable where name like '% John%' and status = 1 limit 1) return id, name, surname"

Тогда ваша программа сделает свое дело, и если она приходит к выводу, что этот поток вообще не должен обрабатывать эту строку, он делает: "update mytable set status = 1 where id =?"

Другая сторона обновляет другие статусы.

0
ответ дан 26 November 2019 в 23:57
поделиться

Нет Нет НООО: -)

Я знаю, что имеет в виду автор. У меня похожая ситуация, и я нашел хорошее решение. Сначала я начну с описания моей ситуации. У меня есть таблица i, в которой я храню сообщения, которые нужно отправить в определенное время. PG не поддерживает выполнение функций по времени, поэтому мы должны использовать демонов (или cron). Я использую специально написанный сценарий, который открывает несколько параллельных процессов. Каждый процесс выбирает набор сообщений, которые необходимо отправить с точностью +1 сек / -1 сек. Сама таблица динамически обновляется новыми сообщениями.

Таким образом, каждый процесс должен загрузить набор строк. Этот набор строк не может быть загружен другим процессом, потому что он создаст много беспорядка (некоторые люди получат пару сообщений, тогда как они должны получить только одно). Вот почему нам нужно заблокировать строки. Запрос на загрузку набора сообщений с блокировкой:

FOR messages in select * from public.messages where sendTime >= CURRENT_TIMESTAMP - '1 SECOND'::INTERVAL AND sendTime <= CURRENT_TIMESTAMP + '1 SECOND'::INTERVAL AND sent is FALSE FOR UPDATE LOOP
-- DO SMTH
END LOOP;

процесс с этим запросом запускается каждые 0,5 секунды. Это приведет к тому, что следующий запрос будет ждать первой блокировки, чтобы разблокировать строки. Такой подход создает огромные задержки. Даже когда мы используем NOWAIT, запрос приведет к исключению, которое нам не нужно, потому что в таблице могут быть новые сообщения, которые необходимо отправить. Если использовать просто FOR SHARE, запрос будет выполняться правильно, но, тем не менее, это займет много времени, что приведет к огромным задержкам.

Чтобы заставить его работать, мы совершаем небольшое волшебство:

  1. изменяем запрос:

     ДЛЯ сообщений в select * from public.messages, где sendTime> = CURRENT_TIMESTAMP - '1 SECOND' :: INTERVAL AND sendTime < = CURRENT_TIMESTAMP + '1 SECOND' :: ИНТЕРВАЛ И отправлено ЛОЖНО И is_locked (msg_id) ЛОЖЬ ДЛЯ ЦИКЛА SHARE
    - ДЕЛАТЬ SMTH
    КОНЕЦ ПЕТЛИ;
    
  2. таинственная функция is_locked (msg_id) выглядит так:

     СОЗДАТЬ ИЛИ ЗАМЕНИТЬ ФУНКЦИЮ is_locked (целое число) ВОЗВРАЩАЕТ БУЛЕВЫЙ КАК $$
    ЗАЯВИТЬ
    id целое число;
    checkout_id целое число;
    is_it логический;
    НАЧИНАТЬ
    checkout_id: = $ 1;
    is_it: = ЛОЖЬ;
    
    НАЧИНАТЬ
     - мы используем FOR UPDATE для попытки блокировки и NOWAIT для немедленного получения ошибки
    id: = msg_id ОТ public.messages ГДЕ msg_id = checkout_id ДЛЯ ОБНОВЛЕНИЯ СЕЙЧАС;
    ИСКЛЮЧЕНИЕ
    КОГДА lock_not_available ТО
    is_it: = ИСТИНА;
    КОНЕЦ;
    
    ВОЗВРАТ is_it;
    КОНЕЦ;
    $$ LANGUAGE 'plpgsql' НЕПОДВИЖНАЯ СТОИМОСТЬ 100;
    

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

Теперь, когда мои 50 процессов работают параллельно, каждый процесс имеет уникальный набор свежих сообщений для отправки. После отправки я просто обновляю строку с помощью sent = TRUE и никогда не возвращаюсь к ней снова.

Надеюсь, это решение подойдет и вам (автор). Если у вас есть какие-либо вопросы, просто дайте мне знать: -)

О, и дайте мне знать, сработало ли это и для вас - тоже.

9
ответ дан 26 November 2019 в 23:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: