sql - Oracle 不能推断这些查询是相同的——生成了非常不同的执行计划

标签 sql performance oracle subquery

我有以下两个非常相似的子查询语句。我用 ** 强调了不同之处。

1:

SELECT DISTINCT name
FROM person, nameindex n
WHERE person.id1    ='0812'
AND person.id2    =n.id2
AND person.id1    =n.id1
AND n.phonetic IN
  (SELECT n2.phonetic
  FROM nameindex n2
  WHERE n2.id1=person.id1 **
  GROUP BY n2.phonetic
 HAVING COUNT(*) BETWEEN 4 AND 500)

2:

SELECT DISTINCT name
FROM person, nameindex n
WHERE person.id1    ='0812'
AND person.id2    =n.id2
AND person.id1    =n.id1
AND n.phonetic IN
  (SELECT n2.phonetic
  FROM nameindex n2
  WHERE n2.id1='0812'  **
  GROUP BY n2.phonetic
 HAVING COUNT(*) BETWEEN 4 AND 500)

我认为 oracle 可以推断,person.id1 在子查询中必须是常量 0812。但是,这两个查询会产生截然不同的执行计划和成本(1:成本 4404211855,而 2:成本:36237)。这是为什么?

这更像是一个分析查询,而不是 OLTP 查询,因此没有为这个特定查询定义索引。

(查询背景:获取 id1='0812' 内在 nameindex 表中出现 4 到 500 次的注音条目的人名。)

最佳答案

我使用以下设置运行了一个类似的查询:

CREATE TABLE person (id1, id2, NAME) AS 
   SELECT to_char(mod(ROWNUM, 1000), 'fm0000'), ROWNUM,
          dbms_random.string('A',10)
     FROM dual 
   CONNECT BY LEVEL <= 1e6;
CREATE TABLE nameindex (id1, id2, phonetic) AS
   SELECT id1, id2, to_char(dbms_random.value(1, 200), 'fm000')
     FROM person;

我发现您的第一个查询生成了以下计划:

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 291343677
--------------------------------------------------------------------------------
| Id  | Operation             | Name      | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |           |     1 |  2040 |   331K  (2)| 01:06:2
|   1 |  HASH UNIQUE          |           |     1 |  2040 |   331K  (2)| 01:06:2
|*  2 |   FILTER              |           |       |       |            |
|*  3 |    HASH JOIN          |           |   891 |  1775K|  1750   (2)| 00:00:2
|*  4 |     TABLE ACCESS FULL | NAMEINDEX |   892 | 18732 |   739   (2)| 00:00:0
|*  5 |     TABLE ACCESS FULL | PERSON    |  1395 |  2750K|  1010   (2)| 00:00:1
|*  6 |    FILTER             |           |       |       |            |
|   7 |     HASH GROUP BY     |           |  9550 | 76400 |   740   (2)| 00:00:0
|*  8 |      TABLE ACCESS FULL| NAMEINDEX |  9550 | 76400 |   739   (2)| 00:00:0
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter( EXISTS (SELECT 0 FROM "NAMEINDEX" "N2" WHERE "N2"."ID1"=:B1
              GROUP BY "N2"."PHONETIC" HAVING "N2"."PHONETIC"=:B2 AND COUNT(*)>=
              COUNT(*)<=500))
   3 - access("PERSON"."ID2"="N"."ID2" AND "PERSON"."ID1"="N"."ID1")
   4 - filter("N"."ID1"='0812')
   5 - filter("PERSON"."ID1"='0812')
   6 - filter("N2"."PHONETIC"=:B1 AND COUNT(*)>=4 AND COUNT(*)<=500)
   8 - filter("N2"."ID1"=:B1)

如您所见,IN 半连接被重写为 EXISTS,它生成与此查询相同的计划:

SELECT DISTINCT NAME
  FROM person, nameindex n
 WHERE person.id1 = '0812'
   AND person.id2 = n.id2
   AND person.id1 = n.id1
   AND EXISTS (SELECT NULL
                 FROM nameindex n2
                WHERE n2.id1 = person.id1
                  AND n2.phonetic = n.phonetic
                GROUP BY n2.phonetic
               HAVING COUNT(*) BETWEEN 4 AND 500);

在这里您可以看到子查询不是常量,因此会针对主查询的每一行进行计算,从而导致执行计划不尽如人意。

我建议您在使用聚合半连接时使用 GROUP BY 中的所有连接列。以下查询产生最优计划:

SELECT DISTINCT NAME
  FROM person, nameindex n
 WHERE person.id1 = '0812'
   AND person.id2 = n.id2
   AND person.id1 = n.id1
   AND (n.id1, n.phonetic) IN (SELECT n2.id1, n2.phonetic
                                 FROM nameindex n2
                                GROUP BY n2.id1, n2.phonetic
                               HAVING COUNT(*) BETWEEN 4 AND 500);

关于sql - Oracle 不能推断这些查询是相同的——生成了非常不同的执行计划,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9902436/

相关文章:

sql - 如何查询复合 GROUP BY 子句中最后一列具有多个值的记录?

sql - 为什么 SQL 引擎在使用 LIMIT 时扫描索引列上的整个表?

java - 如何使用 SQLJ 在 Oracle SQL Developer 控制台上进行打印

java - 代码中的数据库查询

java - 如何使用 JdbcTemplate 从 Spring 创建存储函数?

MySQL 根据另一个表的结果批量更新新表

sql - 需要从 SQL 中的字符串中删除前 11 个字符

python - 计算每个指数平均值的最快方法

android - 一些奥利奥设备没有收到推送通知

穷举 Haskell 模式匹配的性能