【问题标题】:Find which SQL queries from a list of predefined queries are impacted by an INSERT, UPDATE, or DELETE从预定义查询列表中查找哪些 SQL 查询受 INSERT、UPDATE 或 DELETE 影响
【发布时间】:2021-05-20 14:44:22
【问题描述】:

简介

我正在构建一个缓存系统,其中缓存的每个节点都可以从具有 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');

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

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']

开放式问题

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

【问题讨论】:

  • 许多 RDBMS 都有非常高效的内部查询缓存系统,请问您为什么要寻找补充缓存层?是为了研究吗?您正在构建自己的 RDBMS/DBMS 吗?
  • 检查你的技术博客给了我部分答案。您正在寻找 GraphQL 上的缓存?
  • 每个缓存节点实际上执行 0-n 次查询,然后根据这些查询的结果计算一个值。我正在缓存的是这个(相当慢的)计算的结果,而不是任何特定查询的结果。我试图在应该再次计算节点的值时触发缓存失效,因为计算所依赖的一个或多个查询可能已由 INSERT、UPDATE 或 DELETE 更新。我希望这更有意义。
  • 有道理。您的目标是根据数据关系选择性地使缓存条目无效,然后您必须寻找 DML 语句(INSERTUPDATE 等)和与缓存条目关联的查询之间的重叠范围。因此,您寻找一种自动方式来为每个查询构建依赖关系树。我恢复得好吗?
  • 没错!

标签: sql caching cache-invalidation


【解决方案1】:

关于另一种可能的方法,我建议您检查是否可以直接使用您的 RDBMS,如果您的 RDBMS 具有结果缓存能力。一些 RDBMS 可以询问 SQL 查询的 result 缓存状态,从而让您直接了解缓存条目是否仍然有效,而无需解析 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) 之间。)

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

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

【讨论】:

  • 感谢@Zilog80 的详细解释我正在使用Postgres,我相信只有Oracle 具有附加结果缓存功能。也许我可以尝试解析EXPLAIN结果的路线
  • @NicolasKeller 我想一种更有效的方法是通过 pg_constraint.consrc 使用 postgresql 元模式。
猜你喜欢
  • 1970-01-01
  • 2011-10-05
  • 2019-05-17
  • 1970-01-01
  • 1970-01-01
  • 2019-02-07
  • 1970-01-01
  • 2011-11-14
  • 1970-01-01
相关资源
最近更新 更多