【问题标题】:Optimizing DISTINCT SQL query with OR conditions使用 OR 条件优化 DISTINCT SQL 查询
【发布时间】:2012-01-04 18:12:40
【问题描述】:

我有以下 SQL 查询:

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

我试图实现的是获取所有没有记录的唯一业务密钥 concept=case AND attrib=status AND value=closed。在 MySQL 中使用 500 000 条记录和所有唯一业务键运行此查询非常慢:大约 11 秒。

我为business_key 列、concept、attrib 和value 列放置了索引。我还尝试了对所有三列(概念、属性、值)的组合索引,但结果是一样的。

这是EXPLAIN EXTENDED命令的截图:

有趣的是,在没有 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 秒

【问题讨论】:

  • 您是否尝试过将 OR 更改为 AND,如果这是您想要摆脱的?它可能很慢的原因是,在后台,它将查询转换为 3 个查询(OR 各一个),然后过滤掉唯一的。
  • @Neville K:我想要实现的是获取不具有等于概念=case、attrib=status 和 value=close 行的业务键。因此,我不能简单地使用 AND。请查看更新。
  • 此查询返回 500K 行中有多少行?
  • @ypercube:全部。所有业务密钥都是唯一的。
  • @MartinDimitrov 但你还没有回答这个问题。查询返回多少行?

标签: mysql sql performance sql-optimization sqlperformance


【解决方案1】:

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

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

【讨论】:

  • 感谢您的建议,但结果是一样的。需要 10 秒。
【解决方案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 列的长度。如果您希望在一列中最多有 100,则无需声明长度 255

您可以考虑的另一个选择是将这 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' 之类的值似乎重复了很多次。

【讨论】:

  • 这只是示例数据。会有重复business_key的行
  • 好吧,如果您在创建 500K 行样本数据时遇到了麻烦,您可以尝试使用与您预期的真实数据相似的分布(重复的 business_keys、与attrib='status' 相同的行百分比等)。
  • 当我尝试添加索引时 ALTER TABLE memory` ADD INDEX ( concept , attrib , value , business_key ) , the following error prints out: #1071 - 指定的键太长;最大密钥长度为 1000 字节。你知道为什么吗?
  • 字段是CHAR还是VARCHAR类型?
  • 创建一个包含 500K 插入语句的文件根本不是“麻烦”。问题是这样的样本数据是绝对可信的。
【解决方案3】:

如果没有 DISTINCT 查询运行很快,你试过吗:

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

?

【讨论】:

  • 同样的结果。不过,感谢您的建议。
  • 原始脚本会不会运行得更好一些?我看不到改进。如果真的有改进,我想知道。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-15
  • 2013-09-16
  • 2019-11-12
相关资源
最近更新 更多