【问题标题】:Materialized CTE in SQLiteSQLite 中的物化 CTE
【发布时间】:2021-03-13 21:22:42
【问题描述】:

SQLite 3.35.0 包括对materialized CTEs 的支持,

如果使用 MATERIALIZED 短语,则可能会评估 select-stmt 以生成保存在内存或临时磁盘文件中的临时表,当 CTE 表名出现在后续的 SQL。因为 select-stmt 是立即评估的,所以失去了应用优化(例如查询展平或下推优化)的机会。 (然后,CTE 充当“优化围栏”。)

我想构造一个例子来理解新的行为,并想出了以下查询

WITH foo AS MATERIALIZED (
    SELECT RANDOM() FROM (SELECT 1)
) SELECT * FROM foo a, foo b;

使用MATERIALIZED 提示,CTE 应该存储在一个临时表中,但结果包含两个不同的数字,这表明 foo 被计算了两次。

-1208936393171353997|288817028780076083

这是查询计划。令人惊讶的是,玩(NOT) MATERIALIZED 似乎没有任何效果。

addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     1     0                    0
1     Integer        8     1     0                    0
2     Once           0     8     0                    0
3     OpenEphemeral  2     1     0                    0
4     Integer        1     2     0                    0
5     MakeRecord     2     1     3                    0
6     NewRowid       2     4     0                    0
7     Insert         2     3     4                    8
8     Return         1     0     0                    0
9     Integer        16    5     0                    0
10    Once           0     16    0                    0
11    OpenEphemeral  3     1     0                    0
12    Integer        1     6     0                    0
13    MakeRecord     6     1     7                    0
14    NewRowid       3     8     0                    0
15    Insert         3     7     8                    8
16    Return         5     0     0                    0
17    Rewind         2     24    0                    0
18      Rewind         3     24    0                    0
19        Function       0     0     9     random(0)      0
20        Function       0     0     10    random(0)      0
21        ResultRow      9     2     0                    0
22      Next           3     19    0                    1
23    Next           2     18    0                    1
24    Halt           0     0     0                    0

为什么 CTE 没有实现?作为奖励,您能否在 SQLite 3.35.0 和以前的版本下构建具有不同行为的查询(例如,通过具体化将随机数生成器转换为 getRandomNumber,就像我在这里尝试实现的那样)?

【问题讨论】:

  • 应用程序不应该依赖于这个提示的语义效果来决定何时或多久调用用户定义的函数。
  • sqlite 对像random() 这样的非确定性函数所做的事情有时会非常反直觉。查看该查询生成的字节码(参见EXPLAIN)会很有用。
  • @Shawn 我知道你来自哪里,但是 SQLite 非常重视向后兼容性(他们甚至保持与以前版本中的错误的遗留兼容性),所以我很想知道是否有变化实施可能会破坏任何东西。我使用RANDOM 构建了查询,目的是利用其古怪的行为。查询计划将在稍后包含:)
  • 是的,它没有在临时表中保存随机值(只是 rowids)——可能是因为它们是不确定的。存储它们确实会破坏与忽略具体化提示的 CTE 行为的兼容性 - 有关 random() 如何与 CTE 交互的示例,请参见 this question
  • @nalzok 。 .我的猜测是,这是一个未记录的功能,可以在 CTE 非常简单时避免实现。

标签: sql sqlite optimization common-table-expression


【解决方案1】:

问题在于random() 是一个非确定性函数。每次调用它时都允许(并且确实)返回不同的值。 Sqlite 不会缓存此类函数的结果 - 相反,每次引用该函数时都会再次调用该函数。您可以在查询的字节码以及最后在连接中调用它的方式中看到这一点,而不是在用于保存显示的 rowid 的临时表的创建中。

如果它确实缓存了这些表中的随机数,则查询的行为在具体化和非具体化版本之间会有所不同,这与它们只是优化提示的想法相反 - 两者返回的值CTE 的版本应该是等效的并且行为方式相同。

正如物化部分末尾的documentation 所述,

应用程序不应依赖于该提示的语义效果,以了解何时或多久调用用户定义的函数。

如果您希望每次引用给定行时随机生成的数字保持不变,您需要一个真正的临时表,而不是 CTE。

【讨论】:

  • 。 .文档的 sn-p 在哪里? MATERIALIZE (sqlite.org/lang_with.html) 上的实际文档没有提到非确定性函数,并且很清楚 CTE 的结果被存储和重用。这与缓存函数的结果无关。它是关于存储物化视图的结果。
  • @GordonLinoff 它在您和 OP 链接的页面中。
  • 奇怪的是 cte 被物化了两次,尽管这可能是必要的,因为自连接。我不知道它是否支持同一个表中的多个迭代器。
  • 。 .这似乎与本节的其余部分直接矛盾,除了都被警告:“被可能评估”。我认为 SQLite 需要更好的文档编写者。听起来MATERIALIZEDNOT MATERIALIZED 都只是建议——让人想知道两者之间是否有任何区别。我投了赞成票,但我想知道这是由于函数的存在还是由于 SQLite 放弃实现的如此简单的 CTE。
  • @GordonLinoff 物化被明确描述为“非绑定提示”。所以,是的,它们只是建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
  • 2014-04-14
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
  • 1970-01-01
相关资源
最近更新 更多