3

介绍

我正在构建一个缓存系统,其中缓存的每个节点都可以从具有 0-n 参数的预定义、有限的 SQL 查询集中调用任意数量的 SQL 查询。

根据这些查询的结果,节点执行相当慢的计算并返回一个缓存的值。

查询可能如下所示:

查询 #1:

SELECT name 
FROM users 
WHERE id = ?;

查询 #2:

SELECT email 
FROM emails 
WHERE deleted_at IS NULL AND user_id = ?;

其他查询可能使用连接,没有参数或有多个参数,但查询的数量是有限的。

我跟踪每个节点调用的查询和参数集,并建立一个依赖关系列表。然后当查询结果发生变化时,我知道我需要使依赖它的所有缓存节点无效并重新计算它们的值。

问题的核心

现在,困难的部分是知道在我执行 INSERT、UPDATE 或 DELETE 时哪些查询和参数集受到影响。

例子

INSERT INTO users ("id", "name") 
VALUES ('foo', 'John');

此操作将影响带有参数的查询#1 ['foo'],并且所有依赖于具有这些参数的查询的缓存节点都应该失效。

UPDATE users 
SET birth_date = '1990-01-01' 
WHERE id = 'foo';

此操作不会影响查询 #1,因为它不依赖列birth_date来构建其结果。

DELETE FROM users 
WHERE id = 'bar';

['bar']即使在操作后没有行与查询 #1 匹配,这也会影响带有参数的查询 #1。

第一个解决方案

我想出的解决方案有效,但肯定需要改进。

  1. 对于数据库上的每个操作,跟踪一组受影响的行和列:
    INSERT:考虑插入的行及其所有列
    UPDATE:考虑更新之前和之后的行,仅包含更新的列。您最终得到 2 行
    DELETE:考虑删除之前的已删除行及其所有列
  2. 对于在步骤 1 中找到的每一行,找出所有可能受到影响的查询。这是我今天做大量体力工作的地方。我目前正在手动列出每个查询的所有依赖项。示例Q1
const dependencies = [
  {
    table: 'users',
    columns: ['id', 'name'],
    getParams: (row) => [[row.id]], 
  }
]

需要注意的一些有趣的事情:

  • 一个查询在使用join时可能会依赖多个表,所以dependencies是一个数组
  • 我列出了查询所依赖的列,因此可以跳过其他列的更新
  • 通过查看表和列,我们知道行会影响查询
  • 我们需要根据行找到参数集。
    结果是一个数组,因为一行可能会影响具有多个参数集的同一查询。在这个基本示例中,数组的长度仅为 1,因为该行使用 1 个参数集影响查询。

现在考虑以下查询:

UPDATE users 
SET id = 'bar' 
WHERE id = 'foo';

基于步骤 1,我们构建两行:

  • { id: 'foo' }: 更新前行的值
  • { id: 'bar' }: 更新后行的值

请注意,两行都只有id列,因为我们只更新了这一列。现在查看我们在上面构建的依赖项数组,我们知道两行都会影响查询Q1,因为表匹配,并且列重叠(它们都有id列)。

要找到参数集,我需要getParams为每一行调用并将结果展平: [['foo'], ['bar']].

就是这样。我们现在使所有依赖于Q1参数集['foo']或的缓存节点无效['bar']

开放式问题

我正在寻找我可能忽略的任何其他路线。最重要的是,我正在寻找一种自动构建每个查询的依赖关系的方法,手动完成是缓慢、困难且容易出错的。

4

1 回答 1

0

在另一种可能的方法上,我建议您检查是否可以直接使用您的 RDBMS,如果您的 RDBMS 具有结果缓存能力。一些 RDBMS 可以询问 SQL 查询的结果缓存状态,从而让您直接了解缓存条目是否仍然有效,而无需解析 DML 语句。还为至少一个 RDBMS 提供了查询对象依赖项,这可能对您的自动依赖项构建很有用。

临:

  1. 它可以通过查询或一堆查询来完成,RDBMS 会为您完成这项工作。
  2. RDBMS 可以处理更复杂的情况。
  3. 可扩展。
  4. 可靠的。RDBMS 很少出错。
  5. 在使用结果缓存状态选项执行查询时,您只需获取查询的结果缓存 id。

缺点:

  1. 一个大的。您需要至少向 RDBMS 提交一个缓存询问查询以进行查询。这意味着网络 I/O 和延迟。
  2. 您需要配置/调整您的 RDBMS 以使用结果缓存(通常,当 RDBMS 具有此功能时,默认情况下会启用它)。
  3. 在负载较重的 RDBMS 上,有时不会缓存某些查询结果,这意味着相关的缓存条目将失效,从而增加了 RDBMS 的负载...
  4. 注意结果缓存的限制,带有时间戳或序列引用的查询通常会被排除在结果缓存之外。
  5. 结果缓存失效策略可能无法满足您的需求(缓存过期、未细粒度失效等)

对于第一个缺点,处理它的一个好方法是在检查一堆查询的缓存结果之前检查 RDBMS 上的最后一次 DML 执行(例如通过审计表)。仍然存在损失(I/O 延迟),但至少它将最小化 RDBMS 和缓存层的负载。(不可靠,这可能会引发竞争条件。如果您在T有查询,从T(delta2)的审核中获得T(delta1 ) 的“ok no DML” ,您将提交T(delta2)的缓存条目,而DML 可能发生在T(delta1)T(delta2)之间。)

为了说明这一点,您可以EXPLAIN PLAN在 Oracle RDBMS 11+ 上提交一条带有/*+ RESULT_CACHE */提示的语句,以获取查询的结果缓存 ID。然后,您可以稍后使用as查询v$result_cache_objects此缓存 ID并检查该列。如果它不同于( ,等) 或者如果缓存 id 已更改,您可以使缓存条目无效。这也意味着当您填充或刷新它时,您需要获取查询的查询缓存 ID 并将其存储在缓存条目中。并且您应该 在执行查询后立即获取查询的结果缓存 ID ,并从查询 DBMS bloc 中获取它TYPEResultSTATUSPublishedInvalidateSync/*+ RESULT_CACHE */,因此您必须使用能够返回这些数据的 SQL 客户端/接口。
文档在这里。.

对于 SQLServer,AFAIK,直到今天,还没有结果缓存能力,SQLServer 将缓存应用于其输出缓冲区,因此它不能允许这种用法。

于 2021-05-20T19:18:50.197 回答