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 join
是 left 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 = 77
将left 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 NULL
、r.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 计数给了我一个非常糟糕的性能,我做错了吗?的主要内容,如果未能解决你的问题,请参考以下文章