【问题标题】:Make a reliable counter in MySQL在 MySQL 中做一个可靠的计数器
【发布时间】:2017-05-25 21:59:25
【问题描述】:

我想在使用 MySQL 数据库的 PHP 编写的应用程序中创建一个计数器。用户给出一个字符串作为输入。此字符串表示数据库记录的键。它存储一个整数计数器值。用户取回增加的值。

这就是我目前的做法:

// connect to database
try {
    $db = new PDO('mysql:host='.$dbhost.';dbname='.$dbname, $dbuser, $dbpasswd);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch(Exception $ex) {
    echo "DB Error: ".$ex->getMessage();
    exit;
}

// update and get new value
try {
    $db->beginTransaction();
    $nc = updateNC($db, $grundsig, $library);
    $db->commit();
} catch(Exception $ex) {
    $db->rollBack();
    $err = 'DB Error: '.$ex->getMessage();
}

// [...]

function updateNC($db, $grundsig, $library) {

    // increment counter ("nc")
    $query = $db->prepare('
        update numeruscurrens n
        join libraries l using(libid)
        set n.nc = n.nc+1
        where n.grundsig = :grundsig and l.libname = :libname
    ');
    $query->execute([
        ':grundsig' => $grundsig,
        ':libname' => $library
    ]);

    // get new counter value
    $query = $db->prepare('
        select *
        from numeruscurrens n
        join libraries l using(libid)
        where n.grundsig = :grundsig and l.libname = :libname
    ');
    $query->execute([
        ':grundsig' => $grundsig,
        ':libname' => $library
    ]);
    $result = $query->fetchAll();
    return $result[0]['nc'];
}

重要的部分发生在updateNC 函数中。我做了一个更新来增加计数器,我想要新的计数器值。

现在问题出在这两个命令之间,另一个用户可以进行更新。两个用户可能会得到错误的计数器值。

如何可靠地增加一个计数器并为该用户获取新值?

我还考虑过使用像 Redis 这样具有 INCR 命令的键值数据库。但我认为这有点夸张。

【问题讨论】:

  • 我不确定您的尝试是否有意义。但是我能问一下你期望有多少用户吗?以及两个用户同时输入相同键值的几率有多大?它应该接近 0。
  • 我想如果有机会,应该想办法绕过它。一个大约 20 人的团队正在使用这些计数器。如果发生这样的错误,那将是一个很大的问题。中彩票也几乎是不可能的,但有些人会。
  • 他们会向我们这个计数器所有秒/分钟吗?也可以解释你想要做什么/你想用它做什么。我认为有比您正在尝试的解决方案更好的解决方案。另外,如果我理解你,就会有X计数器,既然你说用户可以给计数器的钥匙?那么为什么您团队的所有成员都必须编辑所有计数器?为什么不简单地为每个成员创建一个计数器,给他们一把钥匙,一切都会好起来的..?
  • 该数据库中有 ~40000 个计数器。它们与一个人无关。我从 Redis 添加了有关 INCR 命令的信息,以解释我想要的更多信息。有没有办法在 MySQL 中完成这项工作?

标签: php mysql counter


【解决方案1】:

如果您想要确保他们将获得立即增加的值,那么您应该使用一种方法在提交事务之前锁定您要更新的行。这可以使用FOR UPDATE 提示和之前的 SELECT 查询来锁定您将要更新的行。

我将只添加 SQL 代码以关注实际问题。

SET @counter = 0;

BEGIN;

SELECT n.nc INTO @counter
FROM numeruscurrens n
JOIN libraries l using(libid)
WHERE n.grundsig = @grundsig and l.libname = @libname
FOR UPDATE;

update numeruscurrens n
join libraries l using(libid)
set n.nc = n.nc+1
where n.grundsig = @grundsig and l.libname = @libname

/* You are always incrementing, aren't you? */
SET @counter = @counter + 1;

SELECT @counter;

COMMIT;

另外请注意,这不是免费的。实际上必须要求将此增量值正确返回给用户,因为此锁定会因行锁定而对性能产生负面影响并可能产生死锁。最后但同样重要的是,确保表使用InnoDB 引擎。 MyIsam 不支持行级锁。

【讨论】:

  • 看起来不错。您的解决方案将我引导到文档,其中注意到 MySQL 中仅使用一个表访问的更好方法: UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);选择 LAST_INSERT_ID();
  • 我不知道那个,很整洁。
猜你喜欢
  • 2012-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-12
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
相关资源
最近更新 更多