Mysql 计数给了我一个非常糟糕的性能,我做错了吗?

Posted

技术标签:

【中文标题】Mysql 计数给了我一个非常糟糕的性能,我做错了吗?【英文标题】:Mysql count gives me a very bad performance, am I doing it wrong? 【发布时间】:2013-08-27 02:16:05 【问题描述】:

当我想获取左连接 SQL 的计数时,我需要很长时间, 1分钟后我取消了查询,没有得到结果。

我有两张桌子。 一个是客户,它看起来像:

----------------客户---------------

  `ID` int(11) NOT NULL AUTO_INCREMENT,

  `drpc` int(10) DEFAULT NULL,

  `VIN` varchar(60) COLLATE utf8_bin DEFAULT NULL,

  `cph` varchar(30) COLLATE utf8_bin DEFAULT NULL,

  //... another 60+ columns here

 `invalid` int(1) DEFAULT NULL,

  PRIMARY KEY (`ID`),

  KEY `index_drpc_cph` (`drpc`,`cph`),

  KEY `index_drpc_vin` (`drpc`,`VIN`),

  KEY `index_drpc_invalid` (`drpc`,`invalid`),

  KEY `index_cph` (`cph`)

另一个是repair,看起来是这样的:

-------------修复----

`ID` int(11) NOT NULL AUTO_INCREMENT,

  `drpc` int(10) NOT NULL,

  `cph` varchar(10) DEFAULT NULL,

  `czbh` varchar(15) DEFAULT NULL,

  `gdh` varchar(12) DEFAULT NULL,

  `kdrq` date DEFAULT NULL,

  // ... another 20+ columns here

  `invalid` int(1) DEFAULT '0',

  PRIMARY KEY (`ID`),

  KEY `gmrepair_cph` (`cph`),

  KEY `gmrepair_czbh` (`czbh`),

  KEY `gmrepair_gdh` (`gdh`),

  KEY `gmrepair_drpc_kdrq` (`drpc`,`kdrq`),

  KEY `index_drpc_invalid` (`drpc`,`invalid`),

  KEY `index_drpc_cph` (`drpc`,`cph`)

两个表都有一个字段:'cph'。

最初的要求是:对于给定的drpc,获取那些cph存在于customer但不存在于repair的数据强>。

我的sql语句是这样的:

SELECT * FROM customer c LEFT JOIN 
( SELECT cph FROM repair b WHERE b.drpc=77) r ON c.cph = r.cph 
WHERE c.drpc = 76 AND r.cph IS NULL 

解释结果如下:

顺便说一句, 对于修复表中的drpc = 77,大约有20k条记录;

对于客户表中的drpc = 76,大约有60k条记录。

两个表的存储都是:InnoDB。

执行上面的sql大约需要3秒。

但是,当我想得到上面提到的sql的计数时,我需要很长时间。它甚至无法在 60 秒内完成。

我不确定问题是什么。 能否请您给我一些指点,谢谢一百万!

【问题讨论】:

当你测量 3 秒时,是所有记录都返回还是只出现第一条记录? @GordonLinoff,这不是所有的记录,我很害怕,它只是前 1000 条记录。 短查询可能仍然需要一分钟以上才能获取所有行。 count(*) 必须等到所有行都完成,所以时间比较不是苹果对苹果。 根据您的解释,您应该删除该派生表,即删除 left join ( select ... ) 部分。 【参考方案1】:

尝试左外连接而不是左连接。

SELECT C.*
FROM Customer C
LEFT OUTER JOIN (SELECT cph from 
        FROM Repair WHERE drpc = 77)r  ON C.cph = r.cph
WHERE C.drpc = 76 AND R.cph IS NULL

【讨论】:

呃,left outer joinleft join 的同义词。这不会有什么不同。【参考方案2】:

我的理解是你提供的查询:

SELECT * FROM customer c LEFT JOIN 
( SELECT cph FROM repair b WHERE b.drpc=77) r ON c.cph = r.cph 
WHERE c.drpc = 76 AND r.cph IS NULL 

应该和简单的左连接一样(这是count版本):

select count(*) from customer c
where c.drpc = 76 and c.cph not in (
    select cph from repair where drpc = 77
)

这第二个查询是否也需要太长时间?

【讨论】:

是的,第二个对我来说只是慢的情况,它很慢,select count(*) from (my sql here) t 也无济于事。 我认为没有比第二个更快的查询...它只是一个没有子查询的左连接,根据您的索引,它应该也很有效。 第二个查询不等同于第一个。条件r.drpc = 77left join 转换为inner join @LiuWenbin_NO.好的,我已经编辑了第二个查询......它不会很有效,但应该会给你你正在寻找的结果。试一试...(我想不出没有子查询的解决方案:/) @MostyMostacho,非常感谢您的回复!我重新分析了我的问题,似乎DISTINCT(cph) 现在对我有帮助。所以 SQL 看起来像 SELECT * FROM customer c LEFT JOIN ( SELECT DISTINCT(cph) FROM repair b WHERE b.drpc=77) r ON c.cph = r.cph WHERE c.drpc = 76 AND r.cph IS NULL 。仅需0.3秒。如果有distinct,它只返回 18 行,没有它则返回 20k+。这是目前的关键点,但是,我不能屏住呼吸,我电脑中的数据只是样本数据,而不是真实的生产数据。我会做更多的实验来把它弄清楚。再次感谢!!!【参考方案3】:

查看计划的说明总是有帮助的。看起来应该使用drpc, cph 上的索引进行查询。

但是,如果您的基本查询有效,也许这会给您带来更好的性能。

select count(*)
from (SELECT *
      FROM customer c LEFT JOIN
           (SELECT distinct cph
            FROM repair b
            WHERE b.drpc=77
           ) r
           ON c.cph = r.cph 
      WHERE c.drpc = 76 AND r.cph IS NULL
     ) t;

编辑:

您可以通过这样的查询语句来强制执行计划:

select count(*)
from customer c
where c.drpc = 76 and
      not exists (select 1 from repair r where r.drpc = 77 and r.cph = c.cph);

【讨论】:

非常感谢您的回复。我试过你的sql,但没有帮助。我将在主题中添加解释。再次感谢。 我编辑并添加了解释结果。希望它能够有所帮助,在此先感谢! 非常感谢您的 cmets/回复。现在我觉得我遇到了棘手的问题。如果对于派生表,我使用SELECT DISTINCT(cph) 而不是SELECT cph,我会得到更好的性能。顺便说一句,DISTINCT(cph) 返回 18 行,如果没有 DISTINCT,则返回 20k+。非常感谢!!【参考方案4】:

我不明白为什么其他人没有提到,但是您查询中的子查询不允许有效地使用索引,您实际上在一个有 20k 行的未索引表上留下了连接。

对于查询,您需要 2 个索引: (drpc, cph) 客户和 (cph, drpc) 维修(注意订单,您还没有)。

那你需要重写查询:

SELECT COUNT(*)
FROM  customer c
LEFT JOIN repair r ON c.chp = r.chp AND r.drpc = 77
WHERE c.drpc = 76 AND r.chp IS NULL;

【讨论】:

非常感谢您的指点!是的,子查询是另一个没有任何索引的表。我只是想知道如何使它可索引。我尝试了你的 SQL,它非常快,但是,我无法获得我想要的数据,因为当 r.cph is NULLr.drpc 也是 NULL 时,即使在我之后也没有像 77 这样的东西尝试对您的 SQL 进行一些修改。您能否给我更多指示如何获得正确的数据?我确实需要一个高效的 SQL 来找出 TableA 中存在但 TableB 中不存在的那些数据(对我来说,还有另一个限制,即drpc)。提前一百万谢谢! @LiuWenbin_NO,对不起,r.drpc = 77 一定要进入ON 子句,我会更正我的答案。 非常感谢您的更新!!我会给你一个旋转,并会更新你的结果!再次感谢!!请有一个非常愉快的一天!:)【参考方案5】:

我想我找到了真正的窍门。

这是因为左连接提交cph,这是一个varchar(10),导致执行左连接工作时非常非常慢。

我在两个表上创建一个新列:hash_cph numberic(30,0),然后以这种方式将cph 转换为一些MD5 哈希数: UPDATE customer SET hash_cph = CONV(RIGHT(MD5(cph),16),16,10).

所以我现在可以在新创建的列hash_cph 上应用左连接,它会快得多。 最终的 SQL 如下所示: SELECT COUNT(*) FROM customer c LEFT JOIN repair r ON c.hash_cph= r.hash_cph AND r.drpc = 32 WHERE c.drpc = 1 AND r.hash_cph IS NULL;

顺便说一句,我还在drpc and hash_cph 上为两个表添加了索引。

感谢大家的帮助!!

【讨论】:

以上是关于Mysql 计数给了我一个非常糟糕的性能,我做错了吗?的主要内容,如果未能解决你的问题,请参考以下文章

在这个非常基本的 UIScrollView 中我做错了啥

find() STL 算法...我做错了啥?

DBSCAN 聚类算法无法正常工作。我究竟做错了啥?

将按钮对齐到中心,我做错了啥?

MongoDB:糟糕的 MapReduce 性能

MySQL 使用 select 和 join 插入 post_meta - 我做错了啥?