实际上,您还有两个问题需要解决。
第一个问题是在统计表中插入吞吐量,这可能会比您预期的要早。
另一个问题是如何使用这些统计数据,这也是您在问题中提到的。
让我们从输入吞吐量开始。
首先,如果您这样做,请勿跟踪可能使用缓存的页面的统计信息。使用一个将自己宣传为空JavaScript或一个像素图像的php脚本,并将其包含在您要跟踪的页面上。这样做可以轻松缓存您网站的其余内容。
在电信业务中,与其针对电话通话进行实际插入计费,不如将事物放置在内存中并定期与磁盘同步。这样做可以管理巨大的吞吐量,同时保持硬盘的健康。
为了在您的端口类似地进行操作,您需要原子操作和一些内存存储。以下是一些基于memcache的伪代码,用于执行第一部分...
对于每个页面,您需要一个Memcache变量。在Memcache中,increment()是原子性的,但add(),set()等则不是。因此,当并发进程同时添加相同的页面时,您需要注意不要错误计数点击次数:
$ns = $memcache->get('stats-namespace');
while (!$memcache->increment("stats-$ns-$page_id")) {
$memcache->add("stats-$ns-$page_id", 0, 1800);
$db->upsert('needs_stats_refresh', array($ns, $page_id));
}
定期地,比如每5分钟(相应地配置超时时间),您都希望将所有这些内容同步到数据库中,而不会出现并发进程互相影响或现有点击计数的情况。为此,在进行任何操作之前,您需要增加命名空间(这为所有目的提供了现有数据的锁定),并稍微休眠一下,以便引用先前命名空间的现有进程在需要时完成:
$ns = $memcache->get('stats-namespace');
$memcache->increment('stats-namespace');
sleep(60); // allow concurrent page loads to finish
完成此操作后,您可以安全地循环遍历页面ID,相应地更新统计信息,并清理需要刷新统计信息的表。后者只需要两个字段:page_id int pkey,ns_id int)。然而,这比从脚本运行的简单选择、插入、更新和删除语句更复杂,因此要继续...
正如另一位回答者建议的那样,维护中间统计数据非常合适:存储命中批次而不是单个命中。最多,我假设您想要每小时或每15分钟处理的统计信息,因此可以处理每15分钟批量加载的小计。
更加重要的是,由于您正在使用这些总数对帖子进行排序,因此您希望存储聚合总数并对后者进行索引。(我们将在下面说明。)
一种维护总数的方法是添加触发器,在对统计信息表进行插入或更新时,根据需要调整统计总数。
在这样做时,要特别注意死锁。虽然没有两个$ns
运行会混合它们各自的统计数据,但仍有可能(尽管很小)出现两个或多个进程同时启动上述“增加$ns”的步骤,并随后发出试图并发更新计数的语句。获得咨询锁是避免与此相关问题的最简单、最安全和最快速的方法。
假设您使用了咨询锁,则在更新语句中使用total = total + subtotal 是完全可以的。
在谈到锁时,请注意更新总数将需要对每个受影响的行进行独占锁定。由于您正在按它们排序,因此不希望它们一次性全部处理,因为这可能意味着保持独占锁定的时间较长。最简单的方法是将插入到统计信息中的记录分批处理(例如,每1000个),每个批次后跟一个提交。
对于中间统计数据(每月、每周),在您的统计表中添加一些布尔字段(MySQL 中的 bit 或 tinyint)。让每个字段存储它们是否要计入每月、每周、每日等统计数据。同时,在这些字段上设置触发器,以便它们增加或减少适用总计在您的 stat_totals 表中。
最后,请考虑一下您想要实际计数存储在哪里。它需要是一个索引字段,而后者将被大量更新。通常,您会希望将其存储在自己的表中,而不是页面表中,以避免在页面表中混杂着(更大的)无效行。
假设您已经完成了上述所有步骤,那么您的最终查询将变为:
select p.*
from pages p join stat_totals s using (page_id)
order by s.weekly_total desc limit 10
如果在weekly_total上建立索引,速度应该足够快。
最后,让我们不要忘记最明显的一点:如果您一遍又一遍地运行这些相同的total/monthly/weekly等查询,它们的结果也应该放入memcache中。