【问题标题】:SELECT clause using IN ... very slow?使用 IN ... 的 SELECT 子句非常慢?
【发布时间】:2011-10-25 02:26:19
【问题描述】:

请你们查看以下对 Oracle 数据库的查询并指出问题所在:

SELECT t1.name FROM t1, t2 WHERE t1.id = t2.id AND t2.empno IN (1, 2, 3, …, 200)

查询统计:

  • 耗时:10.53 秒。

指数:

  • t2.empno 已编入索引。

  • t1.id 已编入索引。

  • t2.id 已编入索引。

更新


上述查询只是我使用的查询的示例副本。 下面是更真实的形式

说明计划

查询:

SELECT 
    PRODUCT_REPRESENTATION_SK 
FROM 
    Product_Representation pr 
    , Design_Object do
    , Files files  
    ,EPS_STATUS epsStatus 
    ,EPS_ERROR_CODES epsError
    ,VIEW_TYPE viewTable  
WHERE  
    pr.DESIGN_OBJECT_SK = do.DESIGN_OBJECT_SK 
    AND  pr.LAYER_NAME !='Layer 0' 
    AND  epsStatus.EPS_STATUS_SK = pr.EPS_STATUS  
    AND epsError.EPS_ERROR_CODE = pr.EPS_ERROR_CODE 
    AND viewTable.VIEW_TYPE_ID = pr.VIEW_TYPE_ID 
    AND  files.pim_id = do.PIM_ID 
    AND  do.DESIGN_OBJECT_ID IN
        (
147086,149924,140458,135068,145197,134774,141837,138568,141731,138772,143769,141739,149113,148809,141072,141732,143974,147076,143972,141078,141925,134643,139701,141729,147078,139120,137097,147072,138261,149700,149701,139127,147070,149702,136766,146829,135762,140155,148459,138061,138762.............................................  200 such numbers
        )

索引列:

pr.DESIGN_OBJECT_SK
do.DESIGN_OBJECT_SK
do.DESIGN_OBJECT_ID
files.pim_id

表格


TABLE "PIM"."DESIGN_OBJECT" 
(   
"DESIGN_OBJECT_SK" NUMBER(*,0) NOT NULL ENABLE, 
"PIM_ID" NUMBER(*,0) NOT NULL ENABLE, 
"DESIGN_OBJECT_TYPE_SK" NUMBER(*,0) NOT NULL ENABLE, 
"DESIGN_OBJECT_ID" VARCHAR2(40 BYTE) NOT NULL ENABLE, 
"DIVISION_CD" NUMBER(*,0), 
"STAT_IND" NUMBER(*,0) NOT NULL ENABLE, 
"STAT_CHNG_TMST" TIMESTAMP (6), 
"CRTD_BY" VARCHAR2(45 BYTE), 
"CRT_TMST" TIMESTAMP (6), 
"MDFD_BY" VARCHAR2(45 BYTE), 
"CHNG_TMST" TIMESTAMP (6), 
"UPDATE_CNT" NUMBER(*,0), 
"GENDER" VARCHAR2(1 BYTE), 

 PRIMARY KEY ("DESIGN_OBJECT_SK")
)
TABLESPACE "PIM"  ENABLE, 

FOREIGN KEY ("DESIGN_OBJECT_TYPE_SK")
    REFERENCES "PIM"."DESIGN_OBJECT_TYPE" ("DESIGN_OBJECT_TYPE_SK")
        ON DELETE CASCADE ENABLE, 

FOREIGN KEY ("PIM_ID")
    REFERENCES "PIM"."FILES" ("PIM_ID")
        ON DELETE CASCADE ENABLE

)

表 2


CREATE TABLE "PIM"."PRODUCT_REPRESENTATION" 
(
"PRODUCT_REPRESENTATION_SK" NUMBER(*,0) NOT NULL ENABLE, 
"DESIGN_OBJECT_SK" NUMBER(*,0) NOT NULL ENABLE, 
"VIEW_TYPE_ID" NUMBER(*,0) NOT NULL ENABLE, 
"LAYER_NAME" VARCHAR2(255 BYTE), 
"STAT_IND" NUMBER(*,0) NOT NULL ENABLE, 
"STAT_CHNG_TMST" TIMESTAMP (6), 
"CRTD_BY" VARCHAR2(45 BYTE), 
"CRT_TMST" TIMESTAMP (6), 
"MDFD_BY" VARCHAR2(45 BYTE), 
"CHNG_TMST" TIMESTAMP (6), 
"UPDATE_CNT" NUMBER(*,0), 
"EPS_STATUS" VARCHAR2(30 BYTE) NOT NULL ENABLE, 
"EPS_GENERATED_TIME" TIMESTAMP (6), 
"EPS_ERROR_CODE" NUMBER, 
"EPS_ERROR_DETAILS" VARCHAR2(500 BYTE), 
"DEEPSERVER_ASSET_LAYER_ID" VARCHAR2(255 BYTE), 
"PRODUCT_REPRESENTATION_LOC" VARCHAR2(255 BYTE), 

 PRIMARY KEY ("PRODUCT_REPRESENTATION_SK")
)
TABLESPACE "PIM"  ENABLE, 

FOREIGN KEY ("DESIGN_OBJECT_SK")
    REFERENCES "PIM"."DESIGN_OBJECT" ("DESIGN_OBJECT_SK") 
        ON DELETE CASCADE ENABLE, 
FOREIGN KEY ("VIEW_TYPE_ID")
    REFERENCES "PIM"."VIEW_TYPE" ("VIEW_TYPE_ID")
        ON DELETE CASCADE ENABLE, 

CONSTRAINT "EPS_ERROR_CODE_FK"
FOREIGN KEY ("EPS_ERROR_CODE")
     REFERENCES "PIM"."EPS_ERROR_CODES" ("EPS_ERROR_CODE") 
        ON DELETE CASCADE ENABLE, 
CONSTRAINT "EPS_STATUS_FK" 
FOREIGN KEY ("EPS_STATUS")
    REFERENCES "PIM"."EPS_STATUS" ("EPS_STATUS_SK") 
        ON DELETE CASCADE ENABLE
) 

【问题讨论】:

  • 有什么执行计划吗?似乎解析可能很昂贵,而不是查询部分。 (加上你有一个额外的逗号 - 我认为这是一个粘贴问题)
  • 另外,也许只是你的例子的一个工件......但你应该使用
  • @Sudhakar:这是您的实际查询吗?然后你可以有t2.empno BETWEEN 1 AND 200 或@Randy 和@heximal 建议的类似的。
  • 一般情况下,SQL 引擎在使用 IN 子句时必须通读整个索引。 BETWEEN 更快。
  • 我不得不假设他的 (1,2,3,...,200) 实际上并不像他的示例那么简单。如果他 is 只对BETWEEN 1 AND 200 感兴趣,那么你们当然都是正确的,他应该使用它......但实际上我会假设他对真正的查询使用非顺序和不完整的集合...... .. 假设他正在通过一组 ID 查找特定行

标签: sql oracle sqlperformance sql-tuning


【解决方案1】:

让我们暂时忘记empno BETWEEN 1 and 200 的建议,假设您拥有t2.empno IN (3,7,...,5209)(200 个条目)。

您还可以将查询(这是一个隐藏的 JOIN 查询)写入 non-equivalent EXISTS 查询,该查询将显示相同的结果(但可能行更少)并且应该比 JOIN 更快:

SELECT
    t1.name
FROM
    t1
WHERE EXISTS
      ( SELECT *
        FROM t2
        WHERE t2.id = t1.id
          AND t2.empno IN (3,7,...,5209)
      )

(疯狂猜测)

另一方面,如果不是t2.empno IN (3,7,...,5209),而是t2.empno IN (SELECT tx.empno FROM tx WHERE someConditions) 并且您正在使用MySQL,那么这就是您的问题的根源(已知MySQL无法处理field IN (SELECT f FROM x)以最好的方式)。因此,您可以将查询更改为:

SELECT
    t1.name
FROM
    t1
  JOIN t2
    ON t2.id = t2.id
  JOIN tx
    ON tx.empno = t2.empno
WHERE
    someConditions

甚至:

SELECT
    t1.name
FROM
    t1
WHERE EXISTS
      ( SELECT *
        FROM t2
          JOIN tx
            ON tx.empno = t2.empno
        WHERE t2.id = t1.id
          AND someConditions
      )

【讨论】:

  • +1 关于 MySQL 及其对IN(subquery)的处理的一个很好的观点
【解决方案2】:

错误的第一件事是使用隐式连接语法。那是一个 SQL 反模式。

如果您在 IN 子句中有一个很大的列表,您是否尝试过将它们放在一个表中并使用连接?

什么数据库?您是否查看过您的解释计划或执行计划以了解减速在哪里?

【讨论】:

  • +1 将IN (..) 放入表中可能会提高性能,具体取决于此处所有表的大小...或者,如果您有 SQL2008,则可以将它们发送到 TVP
【解决方案3】:

不要使用交叉连接。

试试这个

SELECT
    t1.name
FROM
    t1
JOIN t2
    ON t2.id = t1.id
WHERE
    t2.empno IN (1,...,200)

编辑:编辑后,看到笛卡尔积中的多个表,使用正确的JOIN 语法可能非常重要。

【讨论】:

  • 这是对该问题的有效答案。否决者,请提供反馈。
  • 我没有反对,因为这是一个很好的建议。但这并不是性能缓慢的答案。对于这样一个简单的查询,任何一种编写查询的方式都应该产生相同的执行计划。
  • @ypercube 你可能是对的,但它仍然是对他的 RDBMS 和他的实际表的推测(而不是他的,我认为,这里过度简化的例子)。尽管如此,与扫描 200 个ints 相比,使用正确的JOIN 可以帮助他的 RDBMS 计划查询并提高速度。使用JOIN 肯定是正确的做法,并且可能会产生他可以处理的结果。
  • 我同意,JOIN 是正确的做法。
  • 我看到的是 t2.id = t2.id 显然是不正确的,否则这里的语法是等价的......没有太多答案。
猜你喜欢
  • 1970-01-01
  • 2011-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-07
  • 1970-01-01
  • 1970-01-01
  • 2012-05-21
相关资源
最近更新 更多