Попробуйте обновить, если обновление не удалось, затем удалите (RubyOnRails-PostgreSQL) - PullRequest
1 голос
/ 21 октября 2019

Я пытаюсь обновить запись, однако, если она не удалась из-за уникальной проверки, я удаляю запись, поскольку предполагаемая запись уже существует.

def update_dependent_model(records, foreign_key, foreign_key_id)
  records.each do |record|
    begin
      ActiveRecord::Base.transaction do
        record.update_column(foreign_key.to_sym, foreign_key_id)
      end
    rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation => e
      ActiveRecord::Base.connection.execute 'ROLLBACK'
      record.delete
    end
  end
end

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

Кроме того, странная вещь о журналах:

  SQL (2.4ms)  UPDATE "records" SET "foriegn_key_id" = 220 WHERE "records"."id" = $1  [["id", 124080]]
  SQL (0.6ms)  UPDATE "records" SET "foriegn_key_id" = 220 WHERE "records"."id" = $1  [["id", 153032]]
  SQL (0.2ms)  UPDATE "records" SET "foriegn_key_id" = 220 WHERE "records"."id" = $1  [["id", 124099]]
  SQL (0.4ms)  UPDATE "records" SET "foriegn_key_id" = 220 WHERE "records"."id" = $1  [["id", 176549]]
  SQL (0.5ms)  UPDATE "records" SET "foriegn_key_id" = 220 WHERE "records"."id" = $1  [["id", 162725]]
  SQL (0.8ms)  UPDATE "records" SET "foriegn_key_id" = 220 WHERE "records"."id" = $1  [["id", 124109]]
   (0.1ms)  ROLLBACK
  SQL (0.3ms)  DELETE FROM "records" WHERE "records"."id" = $1  [["id", 124109]]

На самом деле согласно этому запись с идентификатором 124080 обновлена, но странным образом в БД эта записьвсе еще присутствует.

Ответы [ 2 ]

2 голосов
/ 21 октября 2019

Вы можете инкапсулировать логику в функции на стороне сервера:

CREATE OR REPLACE FUNCTION f_update_or_delete(_id int, _value int) AS
$func$
BEGIN
   UPDATE records
   SET    foriegn_key_id = _value
   WHERE  id = _id;

EXCEPTION WHEN unique_violation THEN   -- error code 23505
   DELETE FROM records WHERE id _id;

END
$func$ LANGUAGE plpgsql;

Но дешевле не вызывать исключение для начала:

WITH cte AS (
   UPDATE records
   SET    foreign_key_id = _value
   WHERE  id = _id
   AND    NOT EXISTS (
      SELECT FROM records
      WHERE  id = _id
      AND    foreign_key_id = _value
      )
   RETURNING 1
   )
DELETE FROM records
WHERE  id = _id
AND    NOT EXISTS (SELECT FROM cte);

Ни то, ни другоеваш оригинал безопасен для параллелизма .

Существует условие гонки между поиском в подзапросе EXISTS и фактическим UPDATE - или следующим DELETE. Чем дольше ваша транзакция не принята, тем выше вероятность того, что вы в итоге удалите строки по ошибке - потому что блокирующая строка была удалена одновременной транзакцией в то же время или параллельная транзакция, вызвавшая уникальное нарушение, была отменена.

Поскольку вы не можете заблокировать строки, которых нет в Postgres, полностью безопасный способ будет состоять в том, чтобы использовать SERIALIZABLE изоляцию транзакции (и повтор при неудаче сериализации) или заблокировать всестол или используйте рекомендательные замки в согласованном порядке.

0 голосов
/ 21 октября 2019

Обернуть всю операцию в одну транзакцию, как показано ниже

def update_dependent_model(records, foreign_key, foreign_key_id)
  ActiveRecord::Base.transaction do
    records.each do |record|
      record.update_column(foreign_key.to_sym, foreign_key_id)
    rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation => e
      record.delete
    end
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...