Обновлено для встроенной поддержки upsert/merge в Slick 2.1.
Внимание
Вы должны использовать простое встраивание SQL с собственным оператором базы данных MERGE. Все попытки смоделировать это утверждение, скорее всего, приведут к неверным результатам.
Задний план:
Когда вы моделируете оператор upsert/merge, Slick должен будет использовать несколько операторов для достижения этой цели (например, сначала выбрать, а затем либо оператор вставки, либо оператор обновления). При выполнении нескольких операторов в транзакции SQL они обычно не имеют того же уровня изоляции, что и один оператор. С различными уровнями изоляции вы будете испытывать странные эффекты в массовых одновременных ситуациях. Так что во время тестов все будет работать нормально, а в продакшене будет происходить странный сбой.
База данных обычно имеет более высокий уровень изоляции при выполнении одного оператора как между двумя операторами в одной и той же транзакции. В то время как один запущенный оператор не будет затронут другими операторами, которые выполняются параллельно. База данных либо заблокирует все, к чему прикасается оператор, либо обнаружит помехи между запущенными операторами и автоматически перезапустит проблемные операторы, когда это необходимо. Этот уровень защиты не сохраняется, когда выполняется следующий оператор в той же транзакции.
Таким образом, может (и будет!) произойти следующий сценарий:
- В первой транзакции оператор select за
user.firstOption
не находит строку базы данных для текущего пользователя.
- Параллельная вторая транзакция вставляет строку для этого пользователя
- Первая транзакция вставляет вторую строку для этого пользователя (аналогично фантомному чтению).
- Вы либо заканчиваете двумя строками для одного и того же пользователя, либо первая транзакция завершается с нарушением ограничения, хотя ее проверка была действительной (когда она выполнялась)
Честно говоря, этого не произойдет с уровнем изоляции "serializable". Но этот уровень изоляции сопряжен с огромным снижением производительности и редко используется в рабочей среде. Кроме того, для сериализации потребуется помощь вашего приложения: система управления базами данных обычно не сериализует всю транзакцию. Но он обнаружит нарушения сериализуемых требований и просто прервет проблемные транзакции. Таким образом, ваше приложение должно быть готово к повторному запуску транзакций, которые прерываются (случайно) СУБД.
Если вы полагаетесь на нарушение ограничения, разработайте приложение таким образом, чтобы оно автоматически перезапускало рассматриваемую транзакцию, не беспокоя пользователя. Это похоже на требование уровня изоляции "сериализуемый".
Заключение
Используйте простой SQL для этого сценария или приготовьтесь к неприятным сюрпризам в рабочей среде. Дважды подумайте о возможных проблемах с параллелизмом.
Обновление 5.8.2014: Slick 2.1.0 теперь имеет встроенную поддержку MERGE.
В Slick 2.1.0 появилась встроенная поддержка оператора MERGE (см. примечания к выпуску: «Поддержка вставки или обновления, которая по возможности использует собственные функции баз данных»).
Код будет выглядеть так (взято из Простые тестовые примеры):
def testInsertOrUpdatePlain {
class T(tag: Tag) extends Table[(Int, String)](tag, "t_merge") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def * = (id, name)
def ins = (id, name)
}
val ts = TableQuery[T]
ts.ddl.create
ts ++= Seq((1, "a"), (2, "b")) // Inserts (1,a) and (2,b)
assertEquals(1, ts.insertOrUpdate((3, "c"))) // Inserts (3,c)
assertEquals(1, ts.insertOrUpdate((1, "d"))) // Updates (1,a) to (1,d)
assertEquals(Seq((1, "d"), (2, "b"), (3, "c")), ts.sortBy(_.id).run)
}
person
stefan.schwetschke
schedule
24.09.2013