2010-05-10 61 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