Как сделать так, чтобы эти две одновременные транзакции всегда выполнялись без нарушения уникальности? - PullRequest
3 голосов
/ 03 января 2012

Давайте представим, что есть таблица tags с уникальным полем с именем name.

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

START TRANSACTION;
SELECT * FROM TAGS WHERE NAME = "FOO";
-- IF A TAG NAMED "FOO" DIDN'T EXIST THEN
INSERT INTO TAGS VALUES("FOO");
COMMIT;

Когда два клиента запускают эту транзакцию с уровнем изоляции по умолчанию (повторяемое чтение), это чередование приведет к сбою одного из них с нарушением уникальности:

START TRANSACTION;
                                                START TRANSACTION;
SELECT * FROM TAGS WHERE NAME = "FOO";
                                                SELECT * FROM TAGS WHERE NAME = "FOO";
-- IF A TAG NAMED "FOO" DIDN'T EXIST THEN
INSERT INTO TAGS VALUES("FOO");
                                                -- IF A TAG NAMED "FOO" DIDN'T EXIST THEN
                                               INSERT INTO TAGS VALUES("FOO");
COMMIT;
                                               COMMIT;

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

Как изменить транзакцию так, чтобы она никогда не завершалась неудачей из-за нарушения ограничения уникальности?

Для справки: это код Ruby on Rails (ActiveRecord), соответствующий этому сценарию:

class Tag < ActiveRecord::Base
  def self.create_tag(name)
    transaction do
      # setting isolation level to serializable leads to a deadlock
      # Tag.connection.execute("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE")
      gets
      tag = Tag.find_by_name(name)
      if tag.nil?
        gets
        Tag.create!(:name => name)
      end
    end
  end
end

Ответы [ 2 ]

2 голосов
/ 03 января 2012

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

INSERT IGNORE INTO TAGS VALUES("FOO");
0 голосов
/ 11 января 2012

Похоже, это тип расы, когда в Rails нет встроенных способов избежать его из коробки.

...