Давайте представим, что есть таблица 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