2010-06-06 54 views
0

我相信每个人都知道线程的并发感。这个SQL会导致任何问题吗?

想象在每一个页面加载以下情形在noobily设置MySQL数据库:

UPDATE stats SET visits = (visits+1) 

如果一千个用户在同一时间加载页面,会自动将计造成任何问题?这是表锁/行锁机制吗?哪一个mysql使用。

+0

应该没问题。我不写这个答案,因为我不完全确定。 – 2010-06-06 05:56:40

回答

3

你有两个潜在的问题:

  1. 你会得到正确的答案?
  2. 你会得到不合理的锁定,你的整个应用程序会非常缓慢甚至是死锁。

正确的答案取决于两个用户是否可以计算(访问+ 1)对相同的访问值。我们可以想像,数据库需要做这些动作:

Read visit count 
    Add one to visit count 
    Write visit count 

因此,如果两个用户在同一时间他们都可以读取相同的旧值的工作?这就是交易的隔离级别起作用的地方。正如Artefacto观察到的默认隔离级别是可重复读,因此我们得到:

Grab a lock 
Read, increment, Write 
Release lock 

不是

Read (two users same old value) 
Increment 
First user Grab Lock, second waits 
Write 
Release, second user grabs lock 
Write (same value!) 
Release 

然而争论的水平可能会相当高,很大程度上取决于范围的交易。假设你有:

Begin transaction 

    Do the visit increment stuff 

    Do some serious business work 

    End transaction <==== visit lock is held until here 

然后你会得到很多人等待访问锁定。我们不知道应用程序的整体结构,无论您是否使用这样的大型事务范围。很可能你得到了每个SQL语句中单个事务的默认行为,在这种情况下,争用只是在SQL语句的持续时间内完成,几乎和你所希望的一样。

其他人可能不那么幸运:有环境(例如Java EE Servlets)隐式事务范围可以由基础架构创建,然后我上面显示的更长时间的事务是默认情况下发生的。更糟糕的是,你的代码是不是一致写入的可能性(与访问增量总是先,或总是最后一个),你可以得到:

Begin transaction 
    Do the visit increment stuff 
    Do some serious business work 
    End transaction <==== visit lock and business locks held until here 

Begin transaction 
    Do some other serious business work 
    Do the visit increment stuff  
    End transaction <==== visit lock and maybesame business locks held until here 

和宾果:死锁

对于大容量网站,您可以考虑将“访问”事件写入队列,并让守护进程监听这些事件并维护计数。更复杂但可能更少的争用问题。

3

不,这不会搞砸了。这在任何符合ACID的数据库中都是完全可以接受的。 I代表分离。这些查询中的每一个都会锁定访问表中的所有行。 A(在ACID中)代表原子性并且表示交易必须全部运行或根本不运行。

+0

如果他没有明确开始交易,该怎么办? – 2010-06-06 06:09:19

+0

我不知道有关MySQL中的事务是否对于noobs来说是automagic? – y2k 2010-06-06 06:10:23

+3

@Sayem Ahmed“在InnoDB中,所有的用户活动都发生在一个事务中,如果启用了自动提交模式,每个SQL语句都会自行形成一个事务。” http://dev.mysql.com/doc/refman/5.1/en/innodb-transaction-model.html – Artefacto 2010-06-06 06:11:01

-1

这很好。
所有的“表锁定/行锁定”是垃圾数据库被发明来照顾。

“千用户同时加载页面”可能还有其他问题,如索引更新。但是这是另一个故事,并且不管怎样,MySQL安装程序都不是这样。

1

确保你有SET autocommit,所以这被视为一个交易,并且数量会很好。唯一的问题是性能(例如具有表热点)

2

对于MySQL,所述manual说:

[重复读]为InnoDB的默认隔离级别。 [...]对于UPDATEDELETE语句,锁定取决于语句是使用具有唯一搜索条件的唯一索引还是范围类型搜索条件。对于具有唯一搜索条件的唯一索引,InnoDB只锁定找到的索引记录,而不是锁定之前的缺口。对于其他搜索条件,InnoDB使用间隙锁或下一个键(间隙加索引记录)锁来锁定扫描的索引范围,以阻止其他会话插入到范围所覆盖的间隙中。

所以我会说是的,你很好,虽然那个特定的查询可能锁定整个表。它可能会更好:

UPDATE stats SET value = value + 1 WHERE key = 'visits' 

带有“key”索引。

2

到目前为止所有的答案似乎假定InnoDB表,它确实支持交易;如果使用MyISAM表,您将获得“原子事务”,这对您的特定用例应该没问题(尽管它们远远低于一般情况下的完整ACID)。

在上交易MySQL的文档(如here)它给你的UPDATE形式为好的做法典型案例,具体地讲,我引用...:

这给我们的东西是 类似于列锁定,但是实际上更好,因为我们只有 更新了一些列,使用 值与其当前值相关的值为 。这意味着, 典型的UPDATE语句看起来 的类似:

UPDATE tablename SET pay_back=pay_back+125; 

...这是非常有效的,即使其他客户端已在pay_back [列]修改的数值,工作

1

这样,如果你工作:

  1. 在交易
  2. 行锁定正在运行设置正确

要特别注意第二点。这不是一件容易的事情,因为MySQL允许你放松锁定约束到一个确实会搞砸的点。另一方面(如果锁定设置正确),如果你碰到一些非常大的交通流量,这可能会成为你的瓶颈(因为它只能在一个单独的线程中执行)。如果你保持交易的开放时间不仅仅是更新这个数字,这个可能性更大,而且如果你不小心的话,它甚至会导致死锁,因为djna会详细解释。

0

正如大家所说,如果您使用InnoDB,这将锁定该行。现在,如果只使用一行,并且该行存储有关所有访问的统计信息,那么在查询等待获取锁时,锁定此行可能会减慢速度。这种放缓可能在您的负载下无法察觉。如果它很重要,你可以通过写入多个不同的行来解决它,比如0-255。这仍然会锁定每一行,但锁定争用的机会现在是原来的1/256。当你想要总数时,你可以对所有行进行求和。

UPDATE stats SET value=value+1 WHERE id=X 

,其中X是一个随机编号0-255

然后

SELECT SUM(value) FROM stats 

会给你真正的总。

相关问题