2010-05-10 114 views
3

我正在製作一個多人遊戲,其中有一個類似大堂的區域,玩家選擇「扇區」進入。大廳網關由PHP支持,而實際的遊戲則由一個或多個Java服務器處理。數據存儲是MySQL。插入一行並避免競爭條件(PHP/MySQL)

快樂的路徑: 玩家選擇一個部門並告訴他想要進入的大廳。 大堂會檢查這是否正常,包括檢查該部門中是否有太多玩家(比較該部門的扇區分配入口數與該部門的max_players值)。 玩家被添加到sector_assignments表中,並將其與該部門配對。玩家客戶端會收到一個密碼,讓他連接到合適的遊戲服務器。

比賽條件: 如果兩個玩家要求在同一時間訪問同一個區域,我可以設想一個情況,他們都被添加了,因爲在他們的支票開始時有一個空間可用並且超過了最大玩家。

是sector_assignments上的最佳解決方案LOCK TABLE?還有其他選擇嗎?

回答

6

通常情況下,爲了解決這樣的併發問題涉及交易和樂觀鎖定:當你更新計數器,加where條款檢查舊值和計數更新的行數。

v = select value from counter where id=x. 
update counter set value = v+1 where value = v and id=x 

如果計數器在此期間更新,該更新將不會改變任何行 - 所以你知道你必須立即回滾並再試一次交易。

一個問題是,它可能會導致很高的爭用,只有少數事務成功和很多失敗。

然後堅持悲觀鎖定可能會更好,您先鎖定行,然後更新它。但只有基準會告訴你。

編輯

如果您使用事務不樂觀鎖定以下情形可能發生。

Max authorized = 50. Current value = 49. 

T1: start tx, read value --> 49 
T2: start tx, read value --> 49 
T1: update value --> 50, acquire a row lock 
T1: commits --> release the lock 
T2: update value --> 50, acquire a row lock 
T2: commits --> release the lock 

兩個事務成功,值爲50,但存在不一致性。

+0

行鎖定對此不起作用,因爲計數器不是行數據的一部分,它是通過在由扇區分組的sector_assignments表上調用COUNT來確定的。看起來我需要在檢查用戶數量之前鎖定表格,然後插入,然後解鎖。 – justkevin 2010-05-12 01:20:24

+0

我明白你的意思了,在你的情況下計數是一個計算值。我有點固執,但如果我是你,我會在扇區的'max_value'旁邊添加一個'current_value'列。這是對數據的輕微的非規範化,但它意味着:沒有'count(*)',也沒有'lock'。只需使用樂觀鎖定,並確保更新'current_value'並在同一事務中的'sector_assignment'中插入行。 – ewernli 2010-05-12 07:22:53

2

如果使用INNODB作爲存儲引擎,則可以在db中使用transactions並避免手動鎖定表。

在單筆交易中,檢查空間是否可用並將玩家添加到該部門。這可以保證檢查查詢的結果在您提交事務之後仍然有效。

+0

請看@ ewernli的答案,爲什麼這是不夠的。抱歉。 – grossvogel 2010-05-11 18:38:51