智能(?)数据库缓存

时间:2010-01-07 12:50:45

标签: php mysql database caching frameworks

我见过几个数据库缓存引擎,所有这些都非常愚蠢(即:keep this query cached for X minutes)并要求您在INSERT / UPDATE之后手动删除整个缓存存储库/ DELETE查询已执行。

大约2或3年前,我为我正在开发的项目开发了一个替代数据库缓存系统,这个想法基本上是使用正则表达式来查找特定SQL查询中涉及的表:

$query_patterns = array
(
    'INSERT' => '/INTO\s+(\w+)\s+/i',
    'SELECT' => '/FROM\s+((?:[\w]|,\s*)+)(?:\s+(?:[LEFT|RIGHT|OUTER|INNER|NATURAL|CROSS]\s*)*JOIN\s+((?:[\w]|,\s*)+)\s*)*/i',
    'UPDATE' => '/UPDATE\s+(\w+)\s+SET/i',
    'DELETE' => '/FROM\s+((?:[\w]|,\s*)+)/i',
    'REPLACE' => '/INTO\s+(\w+)\s+/i',
    'TRUNCATE' => '/TRUNCATE\s+(\w+)/i',
    'LOAD' => '/INTO\s+TABLE\s+(\w+)/i',
);

我知道这些正则表达式可能有一些缺陷(当时我的正则表达式技术非常漂亮)并且显然与嵌套查询不匹配,但是因为我从不使用它们对我来说不是问题。

无论如何,在找到所涉及的表后,我会按字母顺序对它们进行排序,并使用以下命名约定在缓存存储库中创建一个新文件夹:

+table_a+table_b+table_c+table_...+

如果是SELECT查询,我会从数据库中获取结果serialize()并将它们存储在相应的缓存文件夹中,例如以下查询的结果:

SELECT `table_a`.`title`, `table_b`.`description` FROM `table_a`, `table_b` WHERE `table_a`.`id` <= 10 ORDER BY `table_a`.`id` ASC;

将存储在:

/cache/+table_a+table_b+/079138e64d88039ab9cb2eab3b6bdb7b.md5

MD5是查询本身。在随后的SELECT查询中,结果将很容易获取。

如果是任何其他类型的写入查询(INSERTREPLACEUPDATEDELETE等等,我会glob()所有文件夹名称中+matched_table(s)+全部删除所有文件内容。这样就没有必要删除整个缓存,只删除受影响和相关表使用的缓存。

系统工作得很好,性能差异很明显 - 虽然项目的读取查询比写入查询多得多。从那时起,我开始使用事务,FK CASCADE UPDATES / DELETES,并且从未有时间完善系统以使其适用于这些功能。

我过去曾使用MySQL Query Cache,但我必须说性能甚至没有比较。

我想知道:我是唯一一个在这个系统中看到美丽的人吗?有没有我可能不知道的瓶颈?为什么像CodeIgniterKohana这样的流行框架(我不知道Zend Framework)有这样的基本数据库缓存系统?

更重要的是,您认为这是值得追求的功能吗?如果是,我可以做什么/用于使其更快(我的主要关注点是磁盘I / O和(de)序列化查询结果)?< / p>

感谢所有投入,谢谢。

6 个答案:

答案 0 :(得分:2)

我可以看到这个解决方案的美感,但是,我相信它只适用于一组非常具体的应用程序。不适用的情景包括:

  • 利用级联删除/更新或任何类型触发器的数据库。例如,您对表A的DELETE可能会导致表B中的DELETE。正则表达式永远不会捕获它。

  • 从不通过您的缓存无效方案的点访问数据库,例如, crontab脚本等。如果您决定跨机器实现复制(引入只读从站),它也可能会干扰缓存(因为它不会通过缓存失效等)。

即使这些情况对您的情况不切实际,它仍然回答了为什么框架不实现这种缓存的问题。

关于这是否值得追求,这一切都取决于您的申请。也许您想提供更多信息?

答案 1 :(得分:2)

正如您所描述的那样,解决方案存在并发问题的风险。当您每秒接收数百个查询时,您必然会遇到运行UPDATE语句的情况,但在清除缓存之前,SELECT会从中读取并获取过时数据。此外,当几个UPDATE在短时间内达到同一组行时,您可能会遇到问题。

从更广泛的意义上讲,缓存的最佳实践是缓存可能的最大对象。例如,不是在整个地方缓存一堆“用户”相关的行,而是最好只缓存“用户”对象本身。

更好的是,如果您可以缓存整个页面(例如,您向所有人显示相同的主页;配置文件页面看起来几乎与每个人都相同,等等),那就更好了。对整个预渲染页面进行一次缓存获取将显着优于对行/查询级别缓存进行的数十次缓存提取,然后重新刷新页面。

长话短说:个人资料。如果你花时间做一些测量,你可能会发现缓存大型对象,甚至是页面,而不是用来构建这些东西的小查询,是一个巨大的性能胜利。

答案 2 :(得分:1)

虽然我确实看到了这方面的美丽 - 特别是对于资源有限且无法轻松扩展的环境,例如共享主机 - 我个人会担心未来的复杂情况:如果有人,新雇用并且不知道缓存机制,开始使用嵌套查询?如果某些外部服务开始更新表,缓存没有注意到怎么办?

对于一个专门定义的项目,迫切需要通过添加处理器电源或RAM无法提供的加速,这看起来是一个很好的解决方案。作为一个通用组件,我觉得它太不稳定了,并且从长远来看会担心细微的问题,因为人们忘记了有一个缓存需要注意。

答案 3 :(得分:0)

您所描述的改进是为了避免使保证不受更新影响的缓存失效,因为它们从不同的表中提取数据。

这当然很好,但我不确定它是否足够精细以产生真正的差异。您仍然无法使用大量不需要的缓存(因为更新是在桌面上,但在不同的行上)。

此外,即使这种“简单”方案依赖于能够通过查看SQL查询字符串来检测相关表。在一般情况下,由于视图,表别名和多个目录,这可能很难做到。

自动(并且有效地)检测缓存是否需要无效是非常困难的。因此,您可以使用一个非常简单的方案(例如在每个更新或每个表上无效,就像在系统中一样,当有很多更新时效果不好),或者一个非常手工制作的缓存具有深入钩子的特定应用程序(可能难以编写和难以维护),或者接受缓存可以包含陈旧数据并且只是定期刷新它。

答案 4 :(得分:0)

我怀疑正则表达式可能并不适用于所有情况 - 当然它们似乎没有处理混合基表名称和表本身的情况。例如考虑

update stats.measures set amount = 50 where id = 1;

使用统计数据; 更新度量设置金额= 50,其中id = 1;

然后是PL / SQL。

然后事实是它依赖于每个客户选择咨询控制机制,即它预先假定所有数据库访问都来自在共享文件系统上实现缓存控制机制的机器。

(作为一个小点 - 仅仅检查数据文件的修改时间以确定定义的一组表上的查询的缓存版本是否仍然是最新的,而不是尝试识别是否更简单缓存控制机制发现了一个更新 - 它肯定会更加健壮)

稍微退一步,使用健壮的架构从头开始实现这一点意味着所有查询都必须由控制机制拦截。控制机制可能需要更复杂的查询解析器。对于控制机制的所有实例,它当然需要共同的storgae底物。它可能需要理解数据字典 - 所有已经由数据库本身实现的东西。

您声明“我过去使用过MySQL查询缓存,但我必须说性能甚至没有比较。”

我觉得这很奇怪。当然,在处理来自查询的大型结果集时,我的经验是将数据从数据库加载到堆中要比反序列化大型数组快得多 - 尽管大型结果集非常类似于基于Web的应用程序。

当我试图加快数据库访问速度时(当然在修复了其他所有内容之后),我已经走了在多个DBMS实例中复制和分区数据的路线。

下进行。

答案 5 :(得分:0)

这与在主从配置中使用多个数据库时的会话拆分问题有关。基本上,一组类似的正则表达式用于确定正在读取或写入哪些表(甚至哪些行)。系统会跟踪写入哪些表以及何时写入,以及当其中一个表的读取出现时,它将被路由到主表。如果查询正在从其数据不需要精确到秒的表中读取,那么它将被路由到从属。一般来说,当用户自己改变时(即编辑用户的个人资料),信息才真正需要是最新的。

他们在O'Reilly的书中谈到了这一点高性能MySQL。我在开发一个处理会话拆分的系统时用了很多时间。