使用OR条件优化DISTINCT SQL查询

时间:2011-11-23 12:46:42

标签: mysql sql performance sql-optimization sqlperformance

我有以下SQL查询:

SELECT DISTINCT business_key
FROM Memory
WHERE concept <> 'case' OR attrib <> 'status' OR value <> 'closed'

我尝试实现的是获取所有没有记录概念的唯一业务键= case AND attrib = status AND value = closed。在MySQL中运行此查询时,包含所有唯一business_keys的500 000条记录非常慢:大约11秒。

我将索引放在business_key列,概念,attrib和value列中。我还尝试使用所有三列(概念,属性,值)的组合索引,但结果是相同的。

以下是EXPLAIN EXTENDED命令的屏幕截图:

enter image description here

有趣的是,在没有distinct说明符的情况下运行查询会导致执行速度非常快。

我也试过这个:

SELECT DISTINCT m.business_key
FROM Memory m 
WHERE m.business_key NOT IN 
(SELECT c.business_Key 
 FROM Memory c 
 WHERE c.concept = 'case' AND c.attrib = 'status' AND c.value = 'closed')

结果更糟:大约25秒

3 个答案:

答案 0 :(得分:2)

您可以添加复合(concept, attrib, value, business_key)索引,以便查询(如果MySQL决定使用此索引)可以查找索引中的所有信息,而无需读取整个表。

您的查询相当于:

SELECT DISTINCT business_key
FROM Memory
WHERE NOT (concept = 'case' AND attrib = 'status' AND value = 'closed')

和(这可能产生相同的执行计划):

SELECT business_key
FROM Memory
WHERE NOT (concept = 'case' AND attrib = 'status' AND value = 'closed')
GROUP BY business_key

由于要放入索引的4列都是VARCHAR(255),因此索引长度会非常大。 MyISAM不允许超过1000个字节,InnoDB不超过3072个。

一种解决方案是缩短最后一部分的长度,使索引长度小于1000:255+255+255+230 = 995

(concept, attrib, value, business_key(220))

它会起作用,但如果有这么大的索引长度,性能明智,那真是不太好。

另一种选择是降低所有或部分4列的长度,如果这符合您希望存储在那里的数据。如果您希望列中的255最多,则无需声明长度100

您可以考虑的另一个选项是将这4列放在4个单独的参考表中。 (或者只是重复数据的列。似乎business_key将有重复的数据,但不是那么多。因此,为该列创建一个引用表并不是很好。)

示例:将concept值放在新表中,其中包含:

CREATE TABLE Concept_Ref
( concept_id INT AUTO_INCREMENT
, concept VARCHAR(255)
, PRIMARY KEY concept_id
, UNIQUE INDEX concept_idx (concept) 
) ;

INSERT INTO Concept_Ref
  ( concept )
SELECT DISTINCT
    concept
FROM
    Memory ;

然后使用:

更改Memory
ALTER TABLE Memory
ADD COLUMN concept_id INT ;

这样做(一次):

UPDATE 
    Memory m
  JOIN
    Concept_Ref c
      ON c.concept = m.concept
SET m.concept_id = c.concept_id

然后删除Memory.concept列:

ALTER TABLE Memory
DROP COLUMN concept ;

如果您将表格从MyISAM更改为InnoDB,也可以添加FOREIGN KEY个引用。

对所有4列执行相同操作后,不仅Memory表中新复合索引的长度会小得多,而且表的大小也会小得多。此外,使用任何这些列的任何其他索引的长度都会更短。

当然,查询需要写入4个JOIN。此表格中的任何INSERTUPDATEDELETE语句都必须进行更改和精心设计。

但总的来说,我认为你会有更好的表现。使用您现在的设计,似乎'case''status''closed'等值会重复多次。

答案 1 :(得分:1)

这将允许使用索引。检索所有行仍需要一些时间。

SELECT DISTINCT business_key FROM Memory 
WHERE NOT(concept = 'case' AND attrib AND 'status' AND value = 'closed')

答案 2 :(得分:1)

如果查询在没有DISTINCT的情况下快速运行,您是否尝试过:

SELECT DISTINCT business_key from
(SELECT business_key
 FROM Memory
 WHERE concept <> 'case' OR attrib <> 'status' OR value <> 'closed') v