Mysql在高流量数据库上使用过滤器计算行数


Mysql count rows using filters on high traffic database

假设您有一个带有多个选择字段的搜索表单,假设用户从下拉列表中选择一个选项,但在提交数据之前,我需要显示数据库中的行数。

因此,假设该网站每天至少有30万(30万)访客,用户每次访问至少40次从表单中选择选项,这意味着1200万个ajax请求+1200万个数据库计数查询,这似乎有点太多了。

问题是如何实现快速计数(使用php(Zend Framework)和MySQL),这样数据库上额外的12M查询就不会影响网站的负载。

一种解决方案是有一个存储选择字段及其各自计数的所有组合的表(当从产品表中添加或删除产品时,存储计数的表将被更新)。尽管这不是一个好主意,但对于43个过滤器中的8个过滤器(选择选项),将插入+8M个需要管理的行。

关于如何实现这一目标,还有其他想法吗?

附言:我不需要代码示例,但需要在这种情况下工作的想法本身。

我可能会有一个预先计算的表——正如你自己建议的那样。导入是指你有一个用于两件事的智能机制:

  1. 轻松查询哪些条目受到哪些更改的影响
  2. 为整个表单请求提供一个唯一的查找字段

如果你有实心键,8M条目就不会很重要,因为你只需要直接查找。

我会不厌其烦地为这张表写下所有必要的地方的具体更新。即使有大量的更改,这仍然是有效的。如果操作正确,您将知道在插入/更新/删除产品时需要更新或使哪些行无效。

旁注:根据您的评论。如果你需要在八个地方添加代码来覆盖所有可以删除的地方,那么现在可能是重构和集中一些代码的好时机。

有几种情况

  1. mysql有查询缓存,你不必麻烦缓存IF the update of table is not that frequently

  2. 99%用户不会在意匹配了多少结果,he/she just need the top few records

  3. 使用explain-如果您注意到explain将返回查询中要匹配的行数is not 100% precise,但应该足够好,可以用作粗略的行计数

这并不是你想要的,但由于你有很多选项,并且想根据这些选项计算可用的项目,你应该看看Lucene及其分面搜索。它是为了解决这样的问题。

如果你不需要从搜索中获得最新信息,你可以使用队列系统不时向Lucene推送更新和插入(这样你就不必每天为Lucene带来几千次更新和插入)。

您实际上只有三个选项,再多的搜索也不可能发现第四个选项:

  1. 手动计算结果。O(n)和查询时的结果总数
  2. 存储并维护每个过滤器组合的计数。O(1)检索计数,但需要O(2^n)存储和O(2^2)时间在记录更改时更新所有计数
  3. 缓存计数,仅当在缓存中找不到它们时才计算它们(按#1)。当数据在高速缓存中时为O(1),否则为O(n)

正是因为这个原因,那些必须超越琐碎的系统——也就是说,大多数系统——要么限制他们将计数的结果数量(例如,你的GMail收件箱中的项目或谷歌阅读器中的未读项目),要么根据统计数据估计计数(例如,谷歌搜索结果计数),要么两者兼而有之。

我想你可能真的需要对你的用户进行准确的计数,没有任何限制,但很难想象有必要这样做。

我建议使用一个单独的表来缓存计数,并结合触发器。

为了使其快速,您将其作为一个内存表,并使用插入、删除和更新的触发器对其进行更新。

伪代码:

CREATE TABLE counts (
  id unsigned integer auto_increment primary key
  option integer indexed using hash key
  user_id integer indexed using hash key
  rowcount unsigned integer
  unique key user_option (user, option)
) engine = memory
DELIMITER $$
CREATE TRIGGER ai_tablex_each AFTER UPDATE ON tablex FOR EACH ROW
BEGIN
  IF (old.option <> new.option) OR (old.user_id <> new.user_id) THEN BEGIN
    UPDATE counts c SET c.rowcount = c.rowcount - 1 
      WHERE c.user_id = old.user_id and c.option = old.option; 
    INSERT INTO counts rowcount, user_id, option  
      VALUES (1, new.user_id, new.option)
      ON DUPLICATE KEY SET c.rowcount = c.rowcount + 1; 
  END; END IF;
END $$
DELIMITER ;

计数的选择将是即时的,并且触发器中的更新也不应该花费很长时间,因为您使用的内存表具有具有O(1)查找时间的哈希索引。

链接:
内存引擎:http://dev.mysql.com/doc/refman/5.5/en/memory-storage-engine.html
触发器:http://dev.mysql.com/doc/refman/5.5/en/triggers.html

您可以轻松优化的几件事:

  1. 缓存所有您可以缓存的内容。例如,下拉菜单的选项是否需要通过ajax调用获取?这个页面回答了我在实现memcache时的许多问题,当然memcached.org也有很棒的文档。

  2. 提供任何可以静态提供的服务。例如,不经常更改的选项可以通过cron每小时存储在一个平面文件中作为数组,并在运行时包含在脚本中。

  3. 默认配置设置的MySQL对于任何严重的应用程序负载来说通常都是次优的,应该进行调整以适应手头任务的需要。也许可以研究高性能读取访问的内存引擎。

你可以看看这3篇关于物化视图的很棒但非常技术性的文章,事实上,整个博客真的是mysql性能提示的金矿。

GOod luck

假设您正在使用ajax对您所说的后端进行调用。使用某种chached平面文件作为数据的中间文件。将过期时间设置为5秒或其他合适的时间。将数据文件命名为查询关键字=值字符串。在ajax请求中,如果数据文件比冷却时间早,则刷新,如果不是,则使用数据文件中存储的值。

此外,您可能低估了mysql查询缓存机制的强度。如果您使用的是mysql查询缓存,我怀疑以我刚才描述的方式进行查询是否会显著降低性能。如果查询是由mysql缓存的,那么实际上唯一的减速影响将来自应用程序和mysql之间的网络层。

考虑复制在您的体系结构中可以扮演什么角色。如果需要扩展,可以考虑将表从InnoDB复制到MyISAM。如果您正在执行count(*)查询,MyISAM引擎会自动维护表计数。如果您正在执行count(col) where查询,那么您需要在很大程度上依赖于设计良好的标记。在这种情况下,您的计数查询可能会如下所示:

alter table A add index ixA (a, b);
select count(a) using from A use index(ixA) where a=1 and b=2;

我觉得建议这个很疯狂,因为似乎其他人都没有,但你考虑过客户端缓存吗?JavaScript在处理大列表方面并不糟糕,尤其是当它们是相对简单的列表时。

我知道你的理想是希望让数字完全准确,但启发式是你的朋友,尤其是因为同步永远不会是100%——服务器端流量导致的连接缓慢或高延迟会使AJAX请求过时,尤其是如果数据不是恒定的如果数据可以由其他用户编辑,则使用AJAX不可能实现同步。如果其他人无法编辑,则客户端缓存将起作用,并且可能是您的最佳选择。哦,如果你使用某种端口连接,那么无论是什么推送到服务器,都可以简单地更新所有其他客户端,直到完成同步。

如果您愿意进行这种形式的缓存,也可以将结果缓存在服务器上,只需定期刷新查询即可。

正如其他人所建议的,您确实需要在服务器端使用某种缓存机制。无论是MySQL表还是memcache,两者都可以。但是,为了减少对服务器的调用次数,请在一个请求中检索缓存计数的完整列表,并将其缓存在本地javascript中。这是一种非常简单的方法,可以消除近1200万的服务器点击量。

您甚至可以将计数信息存储在一个小时后过期的cookie中,这样以后的页面加载就不需要再次查询了。如果你不需要实时数字的话。

许多最新的浏览器也支持本地存储,而不是像cookie那样,每次请求都会将其传递给服务器

您可以将大量数据放入1-2K json数据结构中。因此,即使你有数千个可能的计数选项,这仍然比你的典型图像要小。如果您使用cookie缓存,请记住最大cookie大小。