【发布时间】: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。
第一个解决方案
我想出的解决方案可行,但肯定需要改进。
- 对于数据库上的每个操作,跟踪一组受影响的行和列:
INSERT:考虑插入的行及其所有列UPDATE:考虑之前的行,并且在它被更新之后,只有更新的列。您最终得到 2 行DELETE:考虑在删除之前删除的行及其所有列 - 对于在步骤 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 语句(
INSERT、UPDATE等)和与缓存条目关联的查询之间的重叠范围。因此,您寻找一种自动方式来为每个查询构建依赖关系树。我恢复得好吗? -
没错!
标签: sql caching cache-invalidation