2008-11-05 110 views
10

如何在MySQL中停止競爭條件?眼前的問題是由一個簡單的算法造成的:mysql插入競爭條件

  1. 從表中選擇一行
  2. ,如果它不存在,將其插入

,然後要麼你得到一個重複行,或如果您通過唯一/主鍵防止出現錯誤。

現在通常我會覺得這裏的交易有所幫助,但由於該行不存在,該交易實際上不利於(或我思念的東西?)。

LOCK TABLE聽起來像是一種矯枉過正,尤其是如果表格每秒更新多次。

唯一的其他解決方案,我能想到的是GET_LOCK()爲每一個不同的ID,但是是不是有更好的辦法?這裏也沒有可擴展性問題嗎?而且,爲每個表做這件事聽起來有點不自然,因爲這聽起來像是高併發性數據庫中一個非常常見的問題。

回答

9

你想要的是LOCK TABLES

,或者如果這似乎過度該行實際上是插入怎麼樣INSERT IGNORE用支票。

如果使用忽略關鍵字,在執行INSERT語句 發生的錯誤 被視爲警告 代替。

+0

使用INSERT IGNORE進行操作。觸發器會導致太多開銷,除非業務邏輯複雜,否則它們是不值得的。 – captainspi 2013-06-08 21:30:03

+0

只是`INSERT`有什麼問題,並且有唯一的約束和捕捉錯誤來說「好吧,記錄已經存在」。 – KeatsKelleher 2014-05-22 14:03:11

4

在我看來,你應該在你的id列上有一個唯一索引,所以重複插入會觸發一個錯誤,而不是再次被盲目接受。

這可以通過將id定義爲主鍵或使用唯一索引本身來完成。

我認爲你需要問的第一個問題是,爲什麼你有多個線程做同樣的工作?爲什麼他們不得不插入完全相同的行?

之後被回答,我認爲只是忽略的錯誤將是最高效的解決方案,但同時測量方法(GET_LOCK V/S忽略錯誤),並看到自己。

我沒有其他方式知道。爲什麼你想避免錯誤?當發生另一種類型的錯誤時,您仍然需要編寫該案例。

由於staticsan說交易做幫助,但,因爲它們通常是隱含的,如果兩個刀片通過不同的線程運行,他們都將是一個隱含的交易中,看到了數據庫的一致意見。

+0

嗯,當然,我們有獨特的想法等等......事實上,這就是我們意識到存在的問題是唯一索引觸發的錯誤。 – tpk 2008-11-05 10:59:35

+2

這就是它應該如何工作,你準備交易失敗......在這種情況下,它似乎很容易:如果重複鍵錯誤,忽略,因爲該行已經存在。全表/行鎖可能更多的是性能問題,而不僅僅是在發生錯誤時忽略錯誤。衡量雖然 – 2008-11-05 11:14:48

2

在技術層面上,交易將幫助這裏,因爲其他線程不會看到新行,直到您提交事務。

但在實踐中不解決問題 - 它只是移動它。您的應用程序現在需要檢查提交失敗並決定要執行的操作。我通常會讓它回滾你所做的事情,然後重新啓動事務,因爲現在該行會顯示。這是基於事務的程序員應該如何工作的。

0

我遇到了同樣的問題,並搜查了淨了一下:)

最後我想出了類似的方法解決creating filesystem objects in shared (temporary) directories to securely open temporary files:

$exists = $success = false; 
do{ 
$exists = check();// select a row in the table 
if (!$exists) 
    $success = create_record(); 
    if ($success){ 
    $exists = true; 
    }else if ($success != ERROR_DUP_ROW){ 
    log_error("failed to create row not 'coz DUP_ROW!"); 
    break; 
    }else{ 
    //probably other process has already created the record, 
    //so try check again if exists 
    } 
}while(!$exists) 

不要害怕busy-的循環 - 通常它會執行一次或兩次。

3

鎖定整個表確實是矯枉過正。爲了達到你想要的效果,你需要一些文獻中稱之爲「謂詞鎖定」的東西。沒有人見過那些除了在學術研究發表的論文上印刷的人。其次,鎖定數據的「訪問路徑」(在某些DBMS中:「頁鎖」)。

一些非SQL系統允許您在一個語句中同時執行(1)和(2),這或多或少意味着您的操作系統在(1)和(2)之間掛起執行線程)完全消除。然而,在沒有謂詞鎖的情況下,這樣的系統仍然需要求助於某種鎖定方案,並且所需鎖的「粒度」(/「範圍」)越精細,併發性越好。

(並得出結論:某些DBMS的 - 特別是那些你不必支付 - 確確實實提供了比「整個表」沒有更細鎖的粒度。)

0

您防止重複行非常簡單地通過把獨特的索引放在你的桌子上。這與LOCKS或TRANSACTIONS無關。

如果插入失敗是因爲它是重複的嗎?如果失敗,你需要通知嗎?或者,插入該行是否重要,並且插入失敗的人數或插入次數無關緊要。

如果你不在乎,那麼你所需要的只是INSERT IGNORE。根本不需要考慮事務或表鎖。

InnoDB自動進行行級鎖定,但僅適用於更新和刪除操作。你說得對,它不適用於插入。你不能鎖定尚不存在的東西!

您可以明確LOCK整個表。但如果你的目的是爲了防止重複,那麼你做錯了。再次使用唯一的索引。

如果存在一組要更改並且您希望得到全有或全無的結果(或者甚至是在更大的全有或全無結果內的一組或全部或全部結果),則使用事務和保存點。然後使用ROLLBACKROLLBACK TO SAVEPOINT *savepoint_name*撤銷更改,包括刪除,更新插入。

LOCK表不是交易的替代品,但它是MyISAM表的唯一選項,它不支持交易。如果行級別鎖定不夠,您還可以將它與InnoDB表一起使用。有關在鎖表語句中使用事務的更多信息,請參閱this page

0

我有類似的問題。我有一個表,在大多數情況下應該有一個唯一的ticket_id值,但有些情況下我會有重複;不是最好的設計,但它是什麼。

  1. 用戶A進行檢查以查看如果票證被保留,這是不
  2. 用戶B進行檢查以查看如果票證被保留,這是不
  3. 用戶B插入一個「保留」記錄進入該票的表格
  4. 用戶A向該票證的表中插入「保留」記錄
  5. 用戶B檢查重複嗎?是的,我的記錄是否更新?是的,離開它
  6. 用戶檢查重複?是的,我的記錄是否更新?不,刪除它

用戶B已預訂票證,用戶A報告該票據已被其他人佔用。

在我的例子中的關鍵是,你需要一個打破平局,在我的情況下,它是行上的自動遞增ID。