гладкая реализация insertIfNotExists 3.0, основанная на не первичном ключе

У меня есть таблица с первичным autoInc key('id'), но мне нужно контролировать уникальность записей в таблице и по другому столбцу (столбец «код»).

Я пытался сделать это так:

def findByCode(code: String): Future[Option[A]] =
  try db.run(tableQuery.filter(_.code === code).result.headOption)


def insert(entity: A): Future[Int] = 
  try db.run(tableQuery += entity)


def insertIfNotExists(code: String, entity: A): Future[Int] = {

  findByCode(code).flatMap {

    case None => insert(entity)
    case _ => Future(-1)
  }
}

Методы findByCode и вставка возвращают Future[A] и Future[Int] соответственно. Когда я запустил этот код для некоторого количества вставляемых записей, я понял, что findByCode не находит записи, которые были вставлены, и я получил массовое дублирование записей. В качестве обходного пути я создал ограничение в своей базе данных (управляемое postgres), но я хотел бы знать, делаю ли я что-то неправильно в коде, или потенциально невозможно гарантировать, что запись существует, когда я только что был вставлен в еще одна параллельная транзакция?

Что может быть оптимальным рекомендуемым способом для моей цели?

1) построить ограничение уникальности и обернуть вставку в блоке try? (я использую это сейчас)

2) реализовать вставкуOrUpdate с помощью plainsql одним запросом (вставить туда, где не существует (выбрать, где...))

3) написать обертку синхронизации для таких запросов (с Await) и запускать их синхронно в одном потоке

4) что-то еще

Заранее спасибо за любые советы.


person Alexander Larin    schedule 09.06.2015    source источник
comment
Мне нравится 1, 2 — вы также можете использовать MERGE/UPSERT. Другие случаи нужны только в том случае, если по какой-то причине уникальное ограничение невозможно или MERGE не работает.   -  person yǝsʞǝla    schedule 09.06.2015
comment
Спасибо, @AlekseyIzmailov. Но есть идеи, почему мой код работает неправильно? Я читал в гладких документах, что безопасность параллелизма не гарантируется. Это мой случай или я просто что-то пропустил?   -  person Alexander Larin    schedule 09.06.2015
comment
Это не может гарантировать, что запись не существует, и может привести к дублированию. Если другой поток вставит запись после того, как вы проверили ее с помощью findByCode, и до того, как вы вставили ее, будет создана дубликат. Если ваша проверка будет довольно медленной, шансы увеличатся.   -  person yǝsʞǝla    schedule 09.06.2015
comment
На самом деле это также зависит от того, делаете ли вы коммит после каждой вставки и какова ваша поддержка/настройки MVCC в БД. В целом, это не лучший способ избежать дубликатов, если вы не разрешаете только одному процессу выполнять вставки, т.е. нет фьючерсов   -  person yǝsʞǝla    schedule 09.06.2015
comment
только что нашел очень похожий вопрос, мой плохой. stackoverflow .com/questions/30706193/   -  person Alexander Larin    schedule 09.06.2015