0

我正在为一个 php 站点编写一个简单的论坛。我正在尝试计算每个类别的帖子数。现在一个类别可以属于另一个类别,根类别被定义为具有 NULL parent_category_id。使用这种架构,一个类别可以有无限数量的子类别,并使表结构保持相当简单。

为了简单起见,假设类别表有 3 个字段:category_idparent_category_idpost_count。我不认为剩余的数据库结构是相关的,所以我暂时不考虑它。

另一个触发器正在调用使该触发器运行的类别表。我想要的是更新帖子数,然后递归地遍历每个父类别,增加帖子数。

DELIMITER $$

CREATE TRIGGER trg_update_category_category_post_count BEFORE UPDATE ON categories FOR EACH ROW
BEGIN
IF OLD.post_count != NEW.post_count THEN
  IF OLD.post_count < NEW.post_count THEN
    UPDATE categories SET post_count = post_count + 1 WHERE categories.category_id = NEW.parent_category_id;
  ELSEIF OLD.post_count > NEW.post_count THEN
    UPDATE categories SET post_count = post_count - 1 WHERE categories.category_id = NEW.parent_category_id;
  END IF;
END IF;
END $$

DELIMITER ;

我得到的错误是:

#1442 - Can't update table 'categories' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. 

我认为您可以在每个页面加载时执行 count() 来计算帖子总数,但在大型论坛上,这会减慢速度,正如此处多次讨论的那样(例如,使用 php 计数帖子或存储在数据库中)。因此,为了将来打样,我将帖子计数存储在表中。为了更进一步,我想我会使用触发器来更新这些计数而不是 PHP。

我知道 MySQL 对在正在更新的同一个表上运行触发器存在限制,这就是导致此错误的原因(即停止无限循环),但在这种情况下,一旦到达具有 NULL parent_category_id 的类别,循环肯定会停止? 无论是调整这个触发器还是完全不同的东西,都必须有某种解决方案。谢谢。

编辑我很欣赏这可能不是最好的做事方式,但它是我能想到的最好的事情。我想如果您将父母类别更改为另一个类别,它会搞砸,但这可以通过另一个重新同步所有内容的触发器来解决。我对如何解决这个问题的其他建议持开放态度。

4

3 回答 3

2

我通常建议不要使用触发器,除非你真的非常需要;递归触发器是引入难以重现的错误的好方法,并且要求开发人员了解一个看似简单的操作的副作用——“我所做的只是将一条记录插入到类别表中,现在整个数据库已锁定向上”。我已经多次看到这种情况发生 - 没有人做错或愚蠢的事情,这只是你冒着副作用的风险。

所以,我只会在你能证明你需要的时候才使用触发器;与其依赖基于一般性的陌生人的意见,我会搭建一个测试环境,放入几百万条测试记录,并尝试优化“在页面加载时计算帖子”解决方案以使其正常工作。

一个可能对此有所帮助的数据库设计是Joe Celko 的“嵌套集”模式——这需要一段时间才能让你头脑清醒,但查询速度可能非常快。

只有当你知道你有一个除了预先计算帖子计数之外你真的无法解决的问题时,我才会考虑使用基于触发器的方法。我会将“帖子计数”分开到一个单独的表格中;这使您的设计更简洁,并且应该解决递归触发问题。

于 2012-07-04T09:38:38.437 回答
1

最简单的解决方案是获取每个类别的所有帖子,然后使用脚本/编程语言将它们链接在一起:

例如在 php 中:

<?php
// category: id, parent, name
// posts: id, title, message
$sql = "select *, count(posts.id) From category left join posts ON posts.cat = category.id Group by category.id";
$query = mysql_query($sql);
$result = array();
while($row = mysql_fetch_assoc($query)){
  $parent = $row['parent'] == null ? 0 : $row['parent'];
  $result[$parent][] = $row;
}
recur_count(0);
var_dump($result);
function recur_count($depth){
    global $result;

    var_dump($result[$depth],$depth); 
       foreach($result[$depth] as $id =>  $o){
          $count = $o['count'];
          if(isset($result[$o['id']])){
             $result[$depth][$id]['count']  += recur_count($o['id']);
           }   

       }   
        return $count;

}
于 2012-07-04T09:51:44.803 回答
0

好的,所以对于想知道我是如何解决这个问题的人来说,我混合使用了触发器和 PHP。

我没有让每个类别更新其父级,而是将其保留为以下结构:一个帖子更新它的线程,然后一个线程使用帖子计数更新它的类别。

然后,我使用 PHP 从数据库中提取所有类别,并使用以下内容循环添加每个帖子计数值:

function recursiveCategoryCount($categories)
{
    $count = $categories['category']->post_count;

    if(!is_null($categories['children']))
        foreach($categories['children'] as $child)
            $count += recursiveCategoryCount($child);

    return $count;  
}

在最坏的情况下,PHP 不会在每个页面加载时添加每个帖子,它只会添加总类别帖子(取决于您所在树中的哪个节点)。这应该非常有效,因为您根据类别的数量将总计算量从 1000 减少到 10 或 100。我还建议每周运行一个脚本来重新计算帖子计数,以防它们变得不同步,就像 phpBB。如果我在使用触发器时遇到问题,那么我会将该功能移到代码中。感谢大家的建议。

于 2012-07-05T13:55:38.850 回答