这个SQL会导致任何问题吗?

时间:2010-06-06 05:55:24

标签: java php c++ python mysql

我确信在涉及线程时,每个人都知道并发的乐趣。

想象一下,在一个设备齐全的MySQL db上的每个页面加载上都有以下场景:

UPDATE stats SET visits = (visits+1)

如果有一千个用户同时加载页面,那么计数会导致任何问题吗?这是表锁定/行锁定机制吗?使用哪一个mysql。

8 个答案:

答案 0 :(得分:3)

不,这不会搞砸。这在任何符合ACID标准的DB中都是完全可以接受的。 I代表隔离。其中每个查询都会锁定访问表中的所有行。 A(在ACID中)代表 Atomicity ,表示交易必须全部运行或根本不运行。

答案 1 :(得分:3)

您有两个潜在的问题:

  1. 你会得到正确的答案吗?
  2. 你会得到不合理的锁定,你的整个应用程序会变得非常缓慢甚至死锁。
  3. 正确的答案取决于两个用户是否可以在相同的访问值上计算(访问+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 Servlet)可以由基础设施创建隐式事务范围,然后默认情况下我上面显示的更长寿命的事务发生。更糟糕的是你的代码编写不一致(访问增量始终是第一个,或总是最后),你可以得到:

      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
    

    宾果游戏:僵局

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

答案 2 :(得分:2)

对于MySQL,manual说:

  

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

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

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

索引为“key”。

答案 3 :(得分:2)

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

在关于交易的MySQL文档中(例如here),它会将您的UPDATE表格作为良好做法典型案例,具体来说,我引用......:< / p>

  

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

UPDATE tablename SET pay_back=pay_back+125;
  

...这非常有效,即使其他客户更改了pay_back [[列]]

中的值,也能正常工作

答案 4 :(得分:1)

确保您拥有SET autocommit,因此这被视为交易,并且计数会很好。唯一的问题是表现(例如有表热点)

答案 5 :(得分:1)

如果您:

,这将有效
  1. 正在进行交易
  2. 正确设置了行锁定
  3. 第二点要特别小心。这不是一个明智的选择,因为MySQL允许你放松锁定约束,以至于这确实会搞砸。

    另一方面(当正确设置锁定时)如果你遇到一些(非常)繁重的流量,这可能会成为你的瓶颈(因为它只能在一个线程中执行)。如果你保持交易开放的时间长于仅更新数字,这就更有可能发生,如果你不小心,djna会详细解释,它甚至可能导致死锁。

答案 6 :(得分:0)

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

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

其中X是随机id 0-255

然后

SELECT SUM(value) FROM stats

会给你真实的总数。

答案 7 :(得分:-1)

没关系 所有“表锁定/行锁定”是废话数据库被发明照顾。

“千位用户同时加载页面”时可能存在其他问题,例如索引更新。但这是另一个故事,无论如何,MySQL设置并非如此。