簡體   English   中英

使用 slick 在 postgres 更新中避免競爭條件

[英]Avoiding race condition in postgres updates using slick

case class Item(id: String, count: Int).  

class ItemRepo(db: Database) {
  val query = TableQuery[ItemTable]


def updateAmount(id: String, incCount :Int) = {
   val currentRow = db.run(query.filter(_.id === id).result).head
   val updatedRow =  Item(currentRow.id, currentRow.count + incCount)
   db.run((query returning query).insertOrUpdate(updatedRow))
}

上面的代碼有一個競爭條件——如果兩個線程並行運行,它們可能都讀取相同的計數,並且只有最后一個更新線程會增加它們的 incCount。

我怎樣才能避免這種情況? 我嘗試在執行query.filter的行中使用.forUpdate ,但它不會阻塞其他線程。 我錯過了什么嗎?

當您從數據庫中獲取數據時,您應該使用SELECT... FOR UPDATE ,以便您在該行上擁有一個排他鎖,以防止其他會話在您的事務完成之前更新數據。

在 Slick 中,您可以使用自 3.2.0 版以來可用forUpdate構造來做到這一點。

您可以使用一些技巧來改善這種情況。

首先,您向數據庫發送兩個獨立的查詢(兩個db.run調用)。 您可以通過將它們組合成單個操作並將其發送到數據庫來改進它。 例如:

// Danger: I've not tried to compile this. Please excuse typos.

val lookupAction = query.filter(_.id === id).result


val updateAction = lookupAction.flatMap { matchingRows =>
   val newItem = matchingRows.headOption match {
      case Some(Item(_, count)) => Item(id, count + incCount)
      case None => Item(id, 1) // or whatever your default is 
   }
   (query returning query).insertOrUpdate(newItem)
}

// and you db.run(updateAction.transactionally)

這將為您帶來某種方式,具體取決於您的數據庫的事務保證。 我提到它是因為在 Slick 中組合動作是一個重要的概念。 這樣,您的forUpdate (Laurenz Albe 指出)可能會按預期運行。

但是,您可能更願意向數據庫發送更新。 您需要使用 Slick 的普通 SQL 功能來執行此操作:

val action = sqlu"UPDATE items SET count = count + $incCount WHERE id = $id"
// And then you db.run(action)

...並允許您的數據庫處理並發性(取決於數據庫隔離級別)。

如果您真的想在所有客戶端執行此操作,在 JVM 上的 Scala 代碼中,有鎖、actor 和refs等並發概念。 Slick 本身沒有任何東西可以為您鎖定 JVM。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM