2013-03-20 256 views
3

我想从多个表中拉出数据,当我用ORDER BY一个日期时间字段它会在至少10秒后返回结果,但如果我做同样的查询没有ORDER BY那么它返回2秒以内的结果。ORDER BY datetime使查询非常缓慢

这是我的当前查询

SELECT 
ph.call_subject AS callSubject, 
ac.account_name AS accountName, 
DATE_FORMAT(ph.trigger_on, "%c/%e/%Y %h:%i %p") AS triggerOn, 
ind.name AS industry, 
cc.call_code_name AS callCode 
FROM phone_calls AS ph 
INNER JOIN accounts AS ac ON ph.account_id = ac.account_id 
INNER JOIN industries AS ind ON ind.industry_id = ac.industry_id 
INNER JOIN call_codes AS cc ON ph.call_code_id = cc.call_code_id 
WHERE ac.status = 1 AND ph.status = 1 AND ph.owner_id = 1 AND ac.do_not_call = 0 
AND ph.trigger_on BETWEEN '2012-11-19 00:00:00' AND '2013-03-19 23:59:59' 
ORDER BY ph.trigger_on ASC LIMIT 0,1000 

下列字段是所有类型INT(11)UNSIGNED

ph.account_id 
ac.account_id 
ind.industry_id 
ac.industry_id 
ph.call_code_id 
cc.call_code_id 
ph.owner_id 

以下字段的全部类型TINYINT(1)

ac.status 
ph.status 
ac.do_not_call 

该字段是日期时间类型

ph.trigger_on 

请注意,有帐户300K记录和phone_calls有500万条记录。 我能做些什么来使ORDER BY更快?请注意,我所有的where子句字段,我所有的ON子句和ph.trigger_on都被编入索引。我使用InnoDB存储引擎而不是MyIsam。

感谢

+4

请包括表定义,所以我们可以看到您选择什么类型和到位 – 2013-03-20 00:06:03

+0

请检查我的职务索引再次为我的一些字段类型更新它 – Jaylen 2013-03-20 00:15:19

+0

请你告诉我们解释什么回报? – redmoon7777 2013-03-20 04:51:23

回答

2

请试试这个:

  1. 建立在列(phone_calls.trigger_on phone_Calls.status,phone_calls索引。owner_id)称之为pcto
  2. 更改FROM子句:

    FROM phone_calls pH值FORCE INDEX pcto

这是理想的。如果它不起作用,那么添加一条评论,我会给你另一种方法,它可以保证工作,并为你提供所需的性能改进。

请注意:在查询中的“每个”列上建立索引并没有关系(并且确实没有好处)。 MySQL只能使用每个表的一个索引(或者更准确地说,每个表别名)。你需要建立我们告诉你的索引。

+0

好的,哇,这个伎俩!你能告诉我你作为选项B的第二个想法是什么,所以我可以有另一个窍门,当在不同的陈述中再次遇到这个问题时使用?谢谢。 – Jaylen 2013-03-20 15:23:44

+0

另一种方法,只有在真正需要时才使用(而且它非常稀少,以至于它是需要的)就是用一个子选择代替FROM子句,因此“FROM(SELECT * FROM phone_calls phs WHERE phs.status = 1和phs.owner_id = 1 ORDER BY phs.trigger_on)AS ph“。你仍然需要在我的答案中建立正确的索引。顺便提一下,现在您已经构建了正确的索引,请在没有“FORCE INDEX pcto”的情况下尝试查询。你可能会发现它仍然可以正常工作。只有索引是不够的,你需要有明智的索引。 – 2013-03-20 15:48:17

+0

非常感谢你让Ben有很多帮助:) – Jaylen 2013-03-21 05:51:32

3

如果你有5行的限制则没有订单查询可以抢前5行找到您搜索的其他条件。

如果你有一个ORDER BY子句,它必须查看所有符合其他条件的行并选择5个最低的行。

+0

我刚刚更新了我的帖子并更改了限制。为了测试的目的,我限制了5个,但它会拉动更多的5个。那么,如何在不失去速度因素的情况下对结果进行排序呢? – Jaylen 2013-03-20 00:18:36

+0

基本上你不能。 ORDER花费的时间是运行完整查询的实际时间。没有ORDER的LIMIT只是掩饰了这一点。如果您的应用程序中有可用内存,则可能会发现查询所有没有ORDER的行并在您的应用程序中排序会更快。请参阅临时表上的此文档http://dev.mysql.com/doc/refman/5.1/en/internal-temporary-tables.html。如果MySQL为你的查询在磁盘上创建一个临时表,将应用程序中的所有内容放入应用程序然后将其排序。 – 2013-04-10 14:37:02

0

根据我的经验,从SQL查询中获得性能的最快方法是将其简化为多个步骤。利用临时表并减少每步的联接和操作次数(吃内存,获得速度)。请原谅我下面,我没有使用MySQL的很长一段时间,现在可能的语法错误,但可以按如下方式重写查询:

CREATE TEMPORARY TABLE scratch1 AS (
    SELECT 
      ph.call_subject AS callSubject, 
      ac.account_name AS accountName, 
      DATE_FORMAT(ph.trigger_on, "%c/%e/%Y %h:%i %p") AS triggerOn, 
      ac.industry_id, 
      ph.call_code_id 
    FROM 
      phone_calls AS ph 
      INNER JOIN accounts AS ac ON ph.account_id = ac.account_id 
    WHERE 
      ac.status = 1 AND ph.status = 1 AND ph.owner_id = 1 AND ac.do_not_call = 0 
      AND ph.trigger_on BETWEEN '2012-11-19 00:00:00' AND '2013-03-19 23:59:59') 

ALTER TABLE scratch1 ADD industry VARCHAR(255) 
ALTER TABLE scratch1 ADD callCode VARCHAR(255) 

UPDATE scratch1 s JOIN industries ind ON ind.industry_id = s.industry_id 
SET s.industry = ind.name 

UPDATE scratch1 s JOIN call_codes cc ON cc.call_code_id = s.call_code_id 
SET s.callCode = cc.call_code_name 

CREATE TEMPORARY TABLE scratch2 AS (
    SELECT * FROM scratch1 ORDER BY triggerOn ASC) 

SELECT * FROM scratch2 LIMIT 0, 1000 
+0

我不能像这样使用临时表。对于一个不适用于每天由系统使用执行1000次查询的过程来说,这是一个很好的解决方案。我从来不使用使用临时表进行常规查询的Web应用程序。 – Jaylen 2013-03-20 00:52:51

+0

虽然在这种情况下,我同意提出的临时表将无济于事,但建议他们仅对过程有用并且仅仅因为您“从不使用[SIC]使用临时表进行常规查询的Web应用程序”并不意味着它们在某些情况下并不完全合理。请不要过来问有经验的程序员的建议,然后声称知道比他们更多。如果你知道那么多,你不需要在这里问,你会! – 2013-03-20 10:56:15

+0

@CaptainPayalytic,我没有说我比你更了解你!我只说过,我从来没有看到这种情况发生在每天执行1000次的查询上。但是,在报告和诱惑项目等其他事情上,你的想法很多。感谢您试图帮助:) – Jaylen 2013-03-20 13:00:31

0

这是阐述Ersun的解决方案/评论。

如果没有order by,SQL会评估查询。在这种情况下,它是一堆连接。很有可能,你在连接字段上有索引。因此,查询通过读取phone_calls中的记录,查找数据,检查过滤条件并返回。然后它会记录下来,等等。总的来说,它可能会读取几千或几万条记录。

对于order by,SQL必须评估全部查询中的记录。它必须读取全部的电话,因为最后一个可能有最小值。然后进行排序并返回正确的记录。

您可以通过让phone_calls(status, owner_id, trigger_on)上的索引满足where子句来加快查询速度。

+0

你的意思是有一个二列表3列?请注意,owner_id值将根据网站中登录的用户标识更改。我有价值1,因为这是我的个人ID。 – Jaylen 2013-03-20 00:55:38

+0

@Mike。 。 。我的意思是你想要一个多列索引表。 – 2013-03-20 01:02:26

+0

但我已经将它们编入索引。我有我的专栏索引中使用的每列。 – Jaylen 2013-03-20 03:16:46

0

当你在SELECT(SELECT)aka上做一个SELECT时,它实际上就像在一个临时表上工作。下面的例子在一个主要的大表上有几个连接。当ORDER BY在整个表查询中时,此解决方案将查询降低到0.2秒,而对于20秒,查询则为20秒。

SELECT * FROM (SELECT `cse_notes`.`notes_id`, `cse_notes`.`dateandtime`, 
    `cse_case`.`case_id`, `cse_case_notes`.`attribute` 
    FROM `cse_notes` 
    INNER JOIN `cse_case_notes` 
    ON `cse_notes`.`notes_uuid` = `cse_case_notes`.`notes_uuid` 
    INNER JOIN `cse_case` 
    ON `cse_case_notes`.`case_uuid` = `cse_case`.`case_uuid` 
    WHERE `cse_notes`.`deleted` = 'N' AND `cse_case`.`case_id` = :case_id 
    AND `cse_notes`.customer_id = :customer_id) notes 
    ORDER BY `dateandtime` DESC 

这是运行速度很慢的错误查询。我认为这很好,我不知道整个表必须在过滤开始之前进行排序。索引本身并没有帮助。

SELECT `cse_notes`.`notes_id`, `cse_notes`.`dateandtime`, 
    `cse_case`.`case_id`, `cse_case_notes`.`attribute`  
    FROM `cse_notes`  
    INNER JOIN `cse_case_notes` ON `cse_notes`.`notes_uuid` = `cse_case_notes`.`notes_uuid`  
    INNER JOIN `cse_case` ON `cse_case_notes`.`case_uuid` = `cse_case`.`case_uuid`  
    WHERE `cse_notes`.`deleted` = 'N' 
    AND `cse_case`.`case_id` = :case_id  
    AND `cse_notes`.customer_id = :customer_id  
    ORDER BY `cse_notes`.dateandtime DESC LIMIT 0, 1000