【问题标题】:Oracle 11g - why is SELECT COUNT(*) infinitely slower than SELECT *?Oracle 11g - 为什么 SELECT COUNT(*) 无限慢于 SELECT *?
【发布时间】:2013-11-07 14:30:30
【问题描述】:

我在 Oracle 11g 中有这样的查询:

SELECT *
FROM CATAT, CG, CCSD
WHERE CATAT.ID = 1007642
AND CG.C_ID = CATAT.ID
AND CATAT.IS_PARENT = 1
AND CCSD.G_ID = CG.ID

在这种情况下,查询返回零行,并且几乎立即执行。但是,如果我将其更改为:

SELECT COUNT(*) AS ROW_COUNT
FROM CATAT, CG, CCSD
WHERE CATAT.ID = 1007642
AND CG.C_ID = CATAT.ID
AND CATAT.IS_PARENT = 1
AND CCSD.G_ID = CG.ID

它永远不会回来 - 我已经让查询运行超过 5 分钟,但它仍然没有完成。事实上,除了 SELECT * 之外的任何东西都需要很长时间才能运行。例如。 SELECT CG.ID FROM...,或SELECT CATAT.* FROM...

这个查询唯一不寻常的是 CCSD 表中有数百万行数据。 CCSD.G_ID上有一个索引,所以不可能是缺少索引。

我只是不明白为什么如果您执行除此之外的任何其他操作,那么使用 SELECT * 立即返回零行的查询会花费这么长时间吗?任何人都可以对此有所了解吗?

更新

这是SELECT * FROM... 查询的解释计划:

这是SELECT COUNT(*) FROM... 查询的解释计划:

【问题讨论】:

  • 你可以为这两个查询输入解释计划吗?
  • 应要求,我已经发布了解释计划!
  • 如果你尝试select count(*) from (<your root select * query>)会发生什么?
  • 尝试 COUNT(1) 而不是 COUNT(*)。
  • Russel.. count(*) 和 count(1) 相同。请参阅以下来自 Tom Kyte asktom.oracle.com/pls/asktom/… 的帖子

标签: sql oracle oracle11g


【解决方案1】:

如果您在某些 SQL 开发环境(如 Toad 或 SQL Developer)中评估查询的性能,那么这并不是真正的比较。大多数 IDE 获取前 n 行(通常为 50 行)。 通过使用 a

包装您的查询
SELECT * FROM (your query) WHERE ROWNUM <= 50

通常带有停止键提示。这意味着数据库只获取前 50 行并停止。但是,您的 SELECT COUNT(*) FROM ... 正在强制数据库实际计算查询返回的每一行,这需要尽可能长的时间。

编辑:当我说您的 SQL Developer 查询包含在 rownum 查询中时,我正在考虑另一个 Oracle 产品 (Apex)。这是不正确的。显然 SQL Developer 会根据您的偏好为您的会话设置一个数组大小。不过,获取 50 行并停止总是比强制计算所有行要快。

编辑 2: 很公平,我以为我理解了这个问题和 SQL Developer 获取大小,但没有。我将在这里留下我的答案作为假设的警示示例。

【讨论】:

  • 这通常是正确的,但是问题中的查询返回 no rows(count(*) 给出 0) - 在这种情况下,Oracle 必须读取表中的所有数据才能在结束结果为空。
  • 您在正确的轨道上,但是 SQL Developer 等工具不会使用 ROWNUM 条件包装查询,它们只是在需要时才获取所有行。
  • 您好,我正在使用 SQL Developer,但这种情况也发生在我使用 oci 库用 PHP 编写的程序中。
  • arraysize 与客户端实际显示的行数无关。这只是一个配置选项,可在检索到行的情况下最大限度地减少网络往返。
【解决方案2】:

如果您改为运行此查询会发生什么?

SELECT COUNT(*) AS ROW_COUNT
FROM CATAT
WHERE CATAT.ID = 1007642
AND CATAT.IS_PARENT = 1
AND EXISTS(SELECT 1 FROM CG WHERE CG.C_ID = CATAT.ID AND EXISTS(SELECT 1 FROM CCSD WHERE CCSD.G_ID = CG.ID))

我认为问题出在查询中的双重联接,

希望对你有帮助!

编辑:

要详细说明,请在您的原始查询中:

SELECT COUNT(*) AS ROW_COUNT
**FROM CATAT, CG, CCSD**
WHERE CATAT.ID = 1007642
AND CG.C_ID = CATAT.ID
AND CATAT.IS_PARENT = 1
AND CCSD.G_ID = CG.ID

第二行是问题所在,当您在 Oracle 的 from 子句中列出其他表时,这意味着您正在编写隐式连接 IF,并且仅当您列出并匹配每个表上的所有主键与不同的另一列桌子。根据您在 where 子句中添加的主键组件,它将导致常规内部连接(如果您匹配所有主键列),或者它可能导致类似于笛卡尔积的结果,我相信计划就是这种情况您在图片中发布,我可以在查询计划中看到带有选项笛卡尔的合并连接。

这一切意味着数据库正在生成一个非常大的表,该表中的行数是 CCSD 中的所有行 * CG 中的所有行 * CATAT 中的所有行(CCSD 有几百万声明,因此这会导致您感知到的缓慢),然后尝试遍历此临时表检查您的过滤器。

出现这个问题是因为原始查询没有针对任务进行优化,而我发布的那个是。

我所做的是阅读您的查询以了解您要做什么,您正在尝试列出具有特定 ID 和 IS_PARENT = 1 的表 CATAT 的子集,但您只想列出那些ID (CATAT.ID) 位于(或存在于)表 CG 和表 CCSD 中的那些。在编写查询时,我尝试使用与条件中相同的级联,但我最初发布的查询也可以这样写:

SELECT COUNT(*) AS ROW_COUNT
FROM CATAT
WHERE CATAT.ID = 1007642
AND CATAT.IS_PARENT = 1
AND EXISTS(SELECT 1 FROM CG WHERE CG.C_ID = CATAT.ID )
AND EXISTS(SELECT 1 FROM CCSD WHERE CCSD.G_ID = CATAT.ID)

现在,这个查询与您编写的原始查询完全相同,但没有连接。为了解决这个查询,数据库通过 ID 和 IS_PARENT 遍历表 CATAT 匹配(有一个索引使得这非常快),一旦一行匹配前两个条件,数据库尝试通过表 CG 上的 C_ID 查找现有记录(再次真的如果您有索引,则速度很快),然后它会尝试按 ID 对表 CCSD 执行相同的操作。这最后 2 次搜索在我发布的第一个查询中是级联的,但想法是一样的:您的查询运行缓慢,因为正在创建一个笛卡尔积(可能已优化,但仍会导致大量行),而我write 只是按 ID 遍历表(没有合并),这些列中可能已经有索引,这就是它运行速度快的原因。

【讨论】:

  • 感谢您的回答!不知何故,这行得通 - 你能解释一下原因吗?
  • 我编辑了答案以进一步解释为什么它工作得更快,希望对您有所帮助
  • 非常感谢您的解释 - 我认为我的程序中可能还有许多其他地方可以从这种优化中受益!
  • 答案如何?您没有解释为什么 Oracle 执行 SELECT 与 COUNT(*) 不同。您发布了原始查询并说它有问题但该查询没有问题并且解释计划与 COUNT 不同
  • 问题不是为什么 Oracle 以不同的方式执行这两个查询,而是关于执行所需的时间,并在 adobe 中进行了解释。
猜你喜欢
  • 2015-07-31
  • 2016-10-09
  • 2014-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-15
  • 2011-09-25
  • 2014-11-27
相关资源
最近更新 更多